6.2. Creating a New Widget
The creation of a completely new widget is the most complex kind of widgets a developer may create.
Warning
We recommened the reader to be certain that existing widgets cannot satisfy display and interaction requirements.
Tip
An updated version of Qt’s Analog Clock Example is provided in the CUT examples. This example
explains how to create a simple widget that draws its own presentation. The updates include: the
use of real/float datatypes for coordinates; access to colors using palette; and a WAF project.
This can be found at examples/analogclock
.
In order to develop a new widget, it is important that the developer understands how palette, fonts, coordinates and size/layout works in Qt.
The new widget will most likely inherit from QWidget. If not, it should inherit from a class that at some point in its ancestors inherits from it.
We encourage that completely New Widgets are developed in C++ and bound to Python.
6.2.1. update() and paintEvent() methods
The QWidget class provides the update()
method. Invoking this
method indicates to Qt that the widget has changes that affect its presentation, and should
queue a paintEvent()
when it estimates necessary. Developer should never invoke
paintEvent()
directly.
When properties that influence presentation are set with new values, the update()
method
should be invoked.
When Qt requests a widget to update its presentation, it uses a QPaintEvent. The method that
receives the QPaintEvent, and implements the drawing instructions of the widget is the paintEvent()
.
Qt uses properties to store the configuration values of the widget, and then paintEvent()
method is used to prepare the pixmap for the presentation. During the course of the paintEvent()
implementation properties can used to draw the pixmap according to the final user’s requests.
6.2.2. event(QEvent*) and specialized event methods
The event(QEvent*)
method is declared in the QObject class but the QWidget has a reimplementation
of it. This reimplemented method processes many interaction events common to widgets and provides
developers with more handy methods to override and customize specific behaviour.
Developers are encourage to override these specialized methods. In case a developer overrides a common behaviour, you should still call the method from the QWidget implementation.
Note
Commonly overriden methods are moveEvent
to get coordinates of the
widget, and the mousePressEvent()/mouseReleaseEvent()
to implement interaction.
A list of the events handled by QWidget and a basic description is included:
- actionEvent
widget’s action have changed.
- changeEvent
widget state has changed. Examples: font, enabled, palette.
- closeEvent
widget received a close request from the window system. Default behaviour is to accept widget and close the window.
- contextMenuEvent
commonly triggered by right clicking on a widget, or using the context menu key.
- dragEnterEvent, dragLeaveEvent, dragMoveEvent
While a drag operation is in progress, these methods are called when entering the area of the widget, when leaving the area of the widget, or while moving above the widget.
- dropEvent
the drag operation finished via drop.
- enterEvent, leaveEvent, mouseMoveEvent
event send when the mouse cursor enters/leaves or moves on the area of the widget.
- focusInEvent, focusOutEvent
widget has gained/lost keyboard focus.
- hideEvent, showEvent
sent when a widget is set to hidden/shown.
- keyPressEvent, keyReleaseEvent
event that indicates a key has been pressed or released.
- mouseDoubleClickEvent, mousePressEvent, mouseReleaseEvent
events triggered when double clicking, when a mouse button is pressed, or released.
- moveEvent
the widget has moved to a different position.
- resizeEvent
the widget’s size has changed.
- tabletEvent
events specific to digitizer tablets, such as WACOM products.
- wheelEvent
mouse or touchpad/trackpad wheel events.
See also
Please refer to QWidget documentation for complete documentation on events.
6.2.3. Colors and Palette
All QWidgets have a reference to a QPalette object. The QWidget::palette()
is propagated
from the the QApplication and modified according to context. The developer is expected to use it
to get the proper colors using ColorRole.
Qt prepares and makes available the palete
member in all QWidget
. This palette
is defined by the system, and the Control GUI Guidelines indicates the ELT uses the system defined
values to set its color scheme.
Warning
Do not deviate from the QWidget’s palette()
values if possible.
When creating QPen and QBrush, please use QPalette::color()
and the appropiate
ColorRole
to obtain colors rom the color scheme. A complete explanation with examples
can be found at QPalette.
6.2.4. Fonts
Fonts are the main manner a GUI communicates information to a User. It is essential to be congruent with the font, since any deviation from the standard one will result in the user having to guess why the font changed.
Warning
Please use QWidget’s font()
to get the current font to render text.
When rendering a widget, introducing text results most of the time having to calculate the height and width of the text. To do this, please use QFontMetrics class to calculate its dimensions. While QFont includes information on the point size of the Font and its family, QFontMetrics is used to do calculation with a QFont.
#include <QFontMetrics>
#include <QFont>
// ...
void AWidget::methodInAWidget(){
QFontMetrics fm(this->font());
int fh = fm.height();
int fw = fm.horizontalAdvance("Text to Render");
}
from PySide2.QtGui import QFontMetrics
from PySide2.QtGui import QFont
from PySide2.QtWidgets import QWidget
// ...
class AWidget(QWidget):
# ...
def method_in_a_widget(self):
fm = QFontMetrics(self.font());
fh = fm.height();
fw = fm.horizontalAdvance("Text to Render");
The height of a font never varies, but he width of a text does due to glyphs width, advance and kerning. Fonts include a kerning table, that indicates how much space should be between two pairs of letters (in case any modification is needed).
QFontMetrics horizontalAdvance()
allows to quickly calculate the width of a string
as rendered with the appropiate QFont.
6.2.5. Coordinates
When drawing with QPainter or QPixmap, the origin of the coordinate system is located at the top-left of the widget area, increasing downwards and eastward.
More details on the coordinate system can be found here CoordinateSystem
Warning
Use real number alternatives. QRectF instead of QRect. QPointF instead of QPoint. This is required by Qt to be compatible with HiDPI scaling.
Tip
[Optional] Translate/Scale the coordinate system
As a first operation when drawing, the developer may introduce a translation, rotation and scaling instruction to effectevily transform the coordinate system.
A common transformation is for radial plotting:
int side = qMin(widgetWidth, widgetHeight);
painter.translate(widgetWidth / 2.0, widgetHeight / 2.0);
painter.scale(side / 100.0, side / 100.0);
This transformation puts the origin at the center of the widget, and scales the coordinates to [-100.0, 100.0]
6.2.6. save() and restore()
The QPainter object is a statefull rasterizer. It will remember translations, rotation and
pen/brush configurations. The QPainter methods save()
restore()
operate on a stack
of states that include pen/brush and transformations.
For every save()
invocation a restore()
call must complete the pair. This is really
helpful when iterating over coordinate position to render diferent elements.
6.2.7. Widget Size and Ratio
Some widgets impose a size restrictions. For example the QDial has a circular representation that requires a 1:1 ratio between height and width. The QKeySequenceEdit widget is as tall as one line of input using the current font.
The developer of a new widget needs to consider if the widget’s size is to be constraint.
Even when the size is contraint, the surface available to draw may be bigger that what the widget needs. Expanding Policies and Default Alignment values could need to be changed in the constructor of the widget.
Also, when drawing, alignment may influence the point where the widget is drawn.
Most widgets include a minimum size. This is commonly the size where the contents would be unreadable and/or the interaction funcitonality is dimished.
6.2.8. Caching Pixmaps
While preparing the pixmap, QPainter object is used. It is possible to create new QPainter objects and draw temporary contents to these objects.
Since properties of the widget are used to draw these cached pixmaps, then when they change, the cache is invalidaded.
If cache is to be implemented, we recommed the introduction of a member invalidatePixmapCache of type bool. Properties setters should invalidate the Pixmap cache, as well as certain QWidget properties. These QWidget properties do not provide signals, but events can be used:
void AWidget::changeEvent(QEvent *event)
{
if( event->type() == QEvent::FontChange ||
event->type() == QEvent::PaletteChange ||
event->type() == QEvent::StyleChange ||
event->type() == QEvent::EnabledChange
){
this->m_invalidatePixmapCache = true;
update();
}
QWidget::changeEvent(event);
}
void AWidget::resizeEvent(QResizeEvent *event)
{
this->m_invalidatePixmapCache = true;
QWidget::resizeEvent(event); }
To render into a QPixmap please follow this example:
void AWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
if( this->m_invalidatePixmapCache ){
this->p_background = std::make_unique<QPixmap>(this->size());
this->p_background->fill(Qt::transparent);
painter->begin(p_background.get());
// ... rendering instructions
painter->end();
}
// ... normal rendering instructions
}
6.2.9. Plugin, Bindings and Example Application
All of this is covered in the Widget Library Tutorial.