您的位置:首页 > 编程语言 > Qt开发

Ranorex automation test qt

2014-01-19 18:59 399 查看

Enabling automation for custom QT widgets by adding Accessibility

Posted by Tobias Walter on Thursday, December 9th, 2010 at 1:31 pm to

Improve Object Recognition



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
Implementing Accessibility
Connecting Accessibility to our Custom Widget
Let’s spy it out

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:



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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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:

view plaincopy
to clipboardprint

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.



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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: