Make automation available for QT applications

Ranorex 5.2 comes with full out-of-the-box support for Qt applications. Please see the Qt testing documentation page.

To make automation available for QT applications, an accessibility interface is provided by QT, which is based on different technologies for each platform QT is available on. In our case MSAA (Microsoft Active Accessibility) is used when working on a Microsoft Windows platform.

QT implements accessibility for most of its built-in widgets, but if you want to add this feature to your own widgets, you have to implement it yourself.

To illustrate the steps that have to be implemented to make a custom widget ready for automation, we want to extend an example widget which shows an analog clock. To provide accessibility for our clock widget, we need to implement an accessible interface for it and send accessible events from the widget when the time changes.

The sample project can be found at “AnalogClock.zip“.


 

QT Accessibility Architecture

The whole structure of the user interface you want to make accessible (“automatable”) is represented by a tree of accessible objects. All of these objects are derived from the class QAccesibleInterface which provides a wide range of predefined information (role, action and relation) that can be defined for these objects. (See QAccessible Class Reference)

So in our example the sub-tree architecture should look something like this:

Tree structure and the relationship between the tree items

Let’s have a look at this tree structure and the relationship between those tree items. We have the clock itself which is mapped as “clock”. And as child elements of the clock we have both the minute hand and the hour hand mapped as “slider”.

As you can see above, our clock consists of three accessible objects: the clock itself, as well as the hour and minute hands. We use an enum ClockElements to identify them. The code for this enumeration is shown later when we start to implement our accessibility interface for the clock widget.

 

Implementing Accessibility

To provide accessibility for our clock widget, we need to implement an accessible interface for it and send accessible events from the widget when the time changes. We will first take a look at the implementation of the interface.

Let’s start with the header file:

class AccessibleClock : public QAccessibleWidget
{
public:

	enum ClockElements {
		ClockSelf = 0,
		HourHand,
		MinuteHand
	};

	AccessibleClock(QWidget *widget, Role role = Client,
		const QString & name = QString());

	int childCount() const;
	QRect rect(int child) const;
	QString text(Text text, int child) const;
	Role role(int child) const;

private:
	AnalogClock *clock() const
	{ return qobject_cast(widget()); }

};

We extend the QAccessibleWidget class which inherits from QAccessibleInterface and helps us handling relationships and keeps track of events, roles, texts and bounding rectangles. Accessible interfaces for all subclasses of QWidget should use QAccessibleWidget as their base class.

Custom interfaces must implement states and give appropriate strings for the Text enum. This is implemented by the text and state functions. Our clock consists of three accessible objects: the clock itself, as well as the hour and minute hands. As described above we use the enum ClockElements to identify them. We also have to define the number of children held by our clock. That can be done with the function childCount. To define the bounding rects of our accessibility objects the function rect is used.

So let’s have a look at the implementation of all of those functions mentioned above, starting with the function which returns the number children our clock element has:

int AccessibleClock::childCount() const
{
	return 2;
}

We return “2”, one for the hour hand and one for the minute hand.

The next function provides the bounding rectangles of our widget:

QRect AccessibleClock::rect(int child) const
{
	QRect rect;
	QPoint topLeft = clock()->mapToGlobal(QPoint(0,0));
	switch (child) {
		  case ClockSelf:
			  rect = clock()->rect();
			  break;
		  case HourHand:
			  rect = clock()->hourHandRect();
			  break;
		  case MinuteHand:
			  rect = clock()->minuteHandRect();
			  break;
		  default:
			  return QAccessibleWidget::rect(child);
	}
	return QRect(topLeft.x() + rect.x(), topLeft.y()
		+ rect.y(), rect.width(), rect.height());
}

As you can see, the child enumeration (or index) is an argument of  the rect() function, so we have to decide which rectangle to return. The rects of the clock and their hands are provided by our analog clock implementation. We only have to calculate the screen coordinates of our rectangles before returning them.

After that we want to define role and text of our analog clock and its child objects:

QAccessible::Role AccessibleClock::role(int child) const
{
	switch (child) {
		case ClockSelf:
			return Clock;
		case HourHand:
		case MinuteHand:
			return Slider;
		default:
			;
	}
	return QAccessibleWidget::role(child);
}

We have decided to use the role clock for our analog clock and the role slider for the clocks hands. You can find the specific roles at QAccessible::Role.

Now we have to define the values for description, name and value for each of our accessibility objects:

QString AccessibleClock::text(Text text, int child) const
{
	if (!widget()->isVisible())
		return QString();

	switch (text) {
		case Description:
			switch (child)
			{
				case ClockSelf:
					return "an Analog Clock";
				case HourHand:
					return "a Hour Hand";
				case MinuteHand:
					return "a Minute Hand";
			}
		case Name:
			switch (child)
			{
				case ClockSelf:
					return "Analog Clock";
				case HourHand:
					return "Hour Hand";
				case MinuteHand:
					return "Minute Hand";
			}
		case Value:
			switch (child)
			{
				case ClockSelf:
					return clock()->currentTime.toString();
				case HourHand:
					return QString("%1").arg(clock()->currentTime.hour());
				case MinuteHand:
					return QString("%1").arg(clock()->currentTime.minute());
			}

		default:
			return QString();
	}
	return QString();
}

After implementing an accessibility interface for our analog clock, we have to somehow inform the accessibility object about changes of state (in our case, when time passes). Therefore we have to hook in the timeout function of our analog clock which is connected to a timer which fires every minute. Here we have to call updateAccesibility to inform our accessibility objects about the new values:

void AnalogClock::timeout()
{
	currentTime = QTime::currentTime();
	QAccessible::updateAccessibility(this, 0, QAccessible::ValueChanged);
	update();
}

 

Connecting Accessibility to our Custom Widget

At this point we have implemented our accessibility interface and we update our state whenever the minute or hour hands move. The next step is to connect the accessibility implementation with the widget itself. This can be done by either implementing an accessibility plug-in or by implementing an interface factory. A plug-in is a class stored in a shared library that can be loaded at run-time. In our example, we choose to implement the interface factory. An explanation of how to implement the plug-in can be found at Accessibility in Qt.

To implement the factory we extend our main.cpp for the factory implementation and in the main function we are installing this factory:

QAccessibleInterface *clockFactory(const QString &classname, QObject *object)
{
	QAccessibleInterface *interface = 0;

	if (classname == "AnalogClock" && object && object->isWidgetType())
		interface = new AccessibleClock(static_cast<QWidget *>(object));
	return interface;
}

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
	QAccessible::installFactory(clockFactory);
	AnalogClock analogClock;
	analogClock.show();
	return a.exec();
}

Our factory is a function pointer for a function that takes two parameters, a QString and a QObject. We check whether the widget an accessible interface is requested for is actually an AnalogClock. If it is, we create and return our custom interface for it.

 

Let’s spy it out

Now we have done everything needed to make our clock accessible. Let’s give it a try. Just compile your project and use Ranorex Spy on the application. You will see that our clock now will have a name, description, role and of course a value. The same applies to the two hands.

Custom QT widgets with Ranorex Spy

As you can see from our example, your custom QT widgets will no longer be an unautomatable black blox if you spend some time implementing basic accessibility. For more information on QT Accessibility, visit Accessibility in Qt.

Download the Sample Project “AnalogClock.zip”.

Related Posts