Wie man die Tastaturnutzung unter Windows mit Qt verfolgt

Qt QML-Entwicklung
2025-01-24
8 Minuten
Tastaturnutzung auf Windows mit Qt im Jahr 2020 verfolgen

Es gibt viele Anwendungsfälle, in denen die Häufigkeit von Benutzereingaben nachverfolgt werden muss. Ein einfaches Beispiel ist eine Zeiterfassungs-App (wie Chronos) mit einer Funktion zur Überwachung der Benutzeraktivität basierend auf Tastatureingaben. Auf diese Weise kann ein Arbeitgeber sicherstellen, dass die Mitarbeiter produktiv sind. Auf den ersten Blick scheint das Problem eine einfache Lösung zu haben – im Fall einer C++-Klasse implementiert man einfach die eventFilter-Methode oder erstellt bei der Arbeit mit QML einen Handler für das Signal Keys.onPressed.

Die Lösung scheint gut zu funktionieren, jedoch nur unter einer entscheidenden Voraussetzung – das App-Fenster muss fokussiert bleiben. Wenn der Benutzer zu einer anderen App wechselt oder die Qt-App minimiert, wird die Tastaturüberwachung nicht mehr durchgeführt, obwohl die Qt-App weiterhin läuft.

Wie kann man also die Tastatureingaben im Hintergrund mithilfe von Qt und QML unter Windows verfolgen? Wir zeigen Ihnen eine Lösung für Windows, die diese Funktionalität bietet. Zerlegen wir das Problem in einzelne Schritte, damit Sie verstehen, wie es im Detail funktioniert.

Wenn Sie das Qt-Framework und seine Funktionen noch nicht kennen, schauen Sie sich diesen Blogbeitrag an.

 

Die Bibliotheken

Zu Beginn sollten Sie eine C++-Klasse erstellen, die sich um die Überwachung der Tastatureingaben kümmert. Da die Lösung auf der QML-Ebene zugänglich sein muss, sollte die C++-Klasse von QObject erben. Anfangs sollte Ihre Klasse folgendermaßen aussehen:

 

#include <QObject>
class KeyboardMonitor : public QObject
{
    Q_OBJECT
public:
    explicit KeyboardMonitor(QObject *parent = nullptr);
signals:
};

Um das Verfolgen von Tastatureingaben im Hintergrund zu ermöglichen, müssen Sie den winuser-Header in Ihr Projekt einbinden. Diese Bibliothek ist Teil der Win32-API, daher ist kein zusätzliches Verlinken von Bibliotheken erforderlich. Mehr darüber können Sie in der Dokumentation nachlesen. Damit diese Bibliothek funktioniert, müssen Sie jedoch auch den windows-Header hinzufügen.

 

#include <QObject>
#include <windows.h>
#include <winuser.h>
class KeyboardMonitor : public QObject
{
    Q_OBJECT
public:
    explicit KeyboardMonitor(QObject *parent = nullptr);
signals:
};

 

Überprüfung des Tastenstatus

Nun haben Sie alle notwendigen Komponenten, um mit dem Tracking zu beginnen. Zunächst müssen Sie den Status der Tasten überprüfen, um festzustellen, ob sie gedrückt sind oder nicht. Die Funktion GetAsyncKeyState ist hierfür am besten geeignet. Wie funktioniert diese Funktion? Die beste Antwort findet sich in der Dokumentation:

Wenn die Funktion erfolgreich ist, gibt der Rückgabewert (vom Typ SHORT) an, ob die Taste seit dem letzten Aufruf von GetAsyncKeyState gedrückt wurde und ob die Taste derzeit gedrückt oder losgelassen ist. Ist das höchstwertige Bit gesetzt, ist die Taste gedrückt; ist das niederwertigste Bit gesetzt, wurde die Taste nach dem vorherigen Aufruf von GetAsyncKeyState gedrückt. Auf dieses letzte Verhalten sollten Sie sich jedoch nicht verlassen.

Die Funktion nimmt den Virtual-Key-Code als Parameter, um festzulegen, welche Taste überprüft werden soll. Die Virtual-Key-Codes reichen von 1 bis 254. Wie oben beschrieben, müssen Sie zur Bestimmung, ob die Taste gedrückt wurde, prüfen, ob das höchstwertige Bit gesetzt ist. Dafür können Sie eine bitweise UND-Operation (mehr dazu hier) mit dem vom GetAsyncKeyState zurückgegebenen Tastenstatus und dem erwarteten Wert durchführen. Da der Rückgabewert vom Typ SHORT ist, hat er eine Länge von 16 Bit. Der Dezimalwert eines SHORT-Typs, bei dem nur das höchstwertige Bit gesetzt ist, beträgt 32.768, was im hexadezimalen Positionssystem 0x100000 entspricht.

Der erste Schritt besteht darin, die Anzahl der vom Benutzer gedrückten Tasten zu zählen. Der einfachste Weg ist, einfach alle Tasten in einer Schleife zu durchlaufen und zu prüfen, ob das höchstwertige Bit in ihrem Statuswert gesetzt ist. Wenn das Bit gesetzt ist, wird die Anzahl der gedrückten Tasten erhöht. Sie können diese Anzahl in einer Variablen wie numberOfActions speichern. Die Funktion (ein Name wie trackActions wäre passend) könnte folgendermaßen aussehen:

 

void KeyboardMonitor::trackActions() {
    for (int i = 1; i < 255; ++i) {
        if(GetAsyncKeyState(i) & 0x100000) {
            _numberOfActions++;
        }
    }
}

 

Zählen

Aktuell können Sie die Anzahl der Tastenanschläge des Benutzers zählen, aber die Funktion muss manuell aufgerufen werden. Wenn Sie sie einfach in eine Endlosschleife setzen, könnte die gesamte App einfrieren, da der GUI-Thread blockiert wird (sofern Sie keine Art von Multithreading implementieren). Keine Sorge – es gibt eine unkomplizierte Lösung, die perfekt zur Qt-Philosophie passt.

Alles, was Sie tun müssen, ist, eine Instanz eines QTimer-Objekts zu erstellen (nennen Sie es beispielsweise _timer) und das timeout-Signal mit der zuvor erstellten Funktion trackActions zu verbinden. Auf diese Weise wird jedes Mal, wenn der Timer ausgelöst wird, die Funktion trackActions aufgerufen. Die Verbindung kann im Klassenkonstruktor durch Hinzufügen einer einzigen Zeile erstellt werden:

 

connect(_timer, &QTimer::timeout, this, &KeyboardMonitor::trackActions);

Nun sollten Sie das Zeitintervall des Timers festlegen. Dieses Intervall gibt an, wie oft der Status der Tasten überprüft wird. Laut Untersuchungen von Kevin Killourhy und Roy Maxion (mehr Informationen dazu hier) beträgt die durchschnittliche Zeit eines einzelnen Tastendrucks etwa 100 ms. Wir werden uns an dieses Intervall halten. Was bleibt, ist der Start des Timers. Der Klassenkonstruktor sollte nun etwa so aussehen:

 

KeyboardMonitor::KeyboardMonitor(QObject *parent) : QObject(parent)
  , _numberOfActions(0)
  , _timer(new QTimer(this)) //timer object initialization
{
    //Connecting timer to function that handles keboard tracking
    connect(_timer, &QTimer::timeout, this, &KeyboardMonitor::trackActions);

    _timer->setInterval(100);
    _timer->start();
}

 

Nutzungsstatistiken

Das Zählen von Tastenanschlägen ist nicht die eleganteste Methode, um die Benutzeraktivität darzustellen. Was passiert, wenn die App kurz nach dem Start viele Aktivitäten zählt und danach keine Eingaben mehr erfasst werden? Das Ergebnis wird normal erscheinen, da der Bezug zur Zeit, in der die Tasten gedrückt wurden, fehlt.

Die Lösung besteht darin, eine Variable zu erstellen, die die Anzahl der Tastenanschläge speichert, die nur innerhalb einer einzigen Sekunde aufgetreten sind – die Historie der Werte kann in einer beliebigen dynamischen Datenstruktur gespeichert werden. Für den Moment erstellen wir jedoch nur eine Variable, um diesen Wert zu speichern. Wenn Sie sehen möchten, wie ein Vektor mit der Historie implementiert wird, schauen Sie sich die Scythe-Studio Github-Seite an.

APS ist ein passender Name für die neue Member-Variable. Dieses Akronym steht für Actions Per Second. Beim Definieren der Variable sollten Sie das Q_PROPERTY-Makro verwenden – es wird uns später sehr helfen, wenn wir versuchen, diese Variable QML zugänglich zu machen. Erstellen Sie einfach ein Property-Makro und nutzen Sie die Refactor-Option im Kontextmenü, um automatisch Setter, Getter und Signal zu generieren.
 

Da wir den Wert von APS nach jeder Sekunde aktualisieren möchten, müssen wir mehrere Timer-Iterationen abwarten, bevor wir die Aktualisierung vornehmen. In diesem Fall vergeht eine Sekunde nach 10 Intervallen von jeweils 100 Millisekunden. Der gleiche Effekt könnte auch durch die Verwendung eines zusätzlichen Timers mit einer Iteration von 1 Sekunde erreicht werden. Allerdings gibt es dabei einen Haken: Da jeder Timer in einem separaten Thread arbeitet, wäre ein Synchronisationsmechanismus (wie ein Mutex) erforderlich. Wir möchten den Code so einfach wie möglich halten, daher bleiben wir bei der Bestimmung des Zeitablaufs mit einem Iterator.

Jetzt müssen Sie in der Funktion trackActions lediglich eine Inkrementieranweisung für den Iterator hinzufügen und die Aktualisierung der APS-Variable vornehmen, wenn der Iterator den Wert 9 erreicht (da wir bei 0 zu zählen beginnen):

 

void KeyboardMonitor::trackActions() {
    for (int i = 1; i < 255; ++i) {
        // The bitwise and selects the function behavior
        if(GetAsyncKeyState(i) & 0x100000) {
            //If the button is pressed number of action is increased
            _numberOfActions++;
        }
    }
    if(_secIterator == 9) {
        _secIterator = 0;
        setAps(_numberOfActions);
        _numberOfActions = 0;
    }
}

 

Exponierung nach QML

An diesem Punkt haben Sie einen voll funktionsfähigen Monitor, der aus C++ zugänglich ist. Wenn Sie die Ergebnisse des Trackings in einer QML-GUI anzeigen möchten, müssen Sie den Zugriff auf die Monitor-Klasse von der QML-Ebene aus ermöglichen. Dank der vorherigen Ergänzungen ist dies ziemlich einfach.

Im ersten Schritt müssen Sie eine Instanz des Monitors in der Datei main.cpp erstellen. Danach sollten Sie dem Engine-Kontext Zugriff auf den Monitor gewähren, damit er aus QML zugänglich ist. Dies kann mit der Funktion setContextProperty erfolgen, für die der Header QQmlContext eingebunden werden muss.

 

KeyboardMonitor* keyboardMonitor = new KeyboardMonitor();
QQmlApplicationEngine engine;
//First parameter is name of object that you will use to access it from QML, second is a pointer to it
engine.rootContext()->setContextProperty("keyboardMonitor", keyboardMonitor);

Now you can access monitor object in any QML file inside your project. Thanks to the Q_PROPERTY macro used before, all the variables decorated with it can be accessed like standard QML properties, including property binding, and using signals like onValueChanged.

 

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
  visible: true
  width: 640
  height: 480
  title: qsTr("Hello World")
  Page {
    id: mainPage
    anchors.fill: parent
    Text {
      id: apsLabel
      anchors {
        bottom: apsValue.top
        horizontalCenter: parent.horizontalCenter
        bottomMargin: 40
      }
      font.pointSize: 30
      text: qsTr("APS")
    }
    Text {
      id: apsValue
      anchors.centerIn: parent
      font.pointSize: 60
      text: keyboardMonitor.aps
    }
  }
}

Jetzt können Sie sehen, wie die App funktioniert:

 

 

Erfahren Sie mehr

Wenn Sie noch wissensdurstig sind, finden Sie das gesamte Projekt und den Quellcode mit einigen zusätzlichen Funktionen auf der Scythe-Studio Github-Seite. Vergessen Sie nicht, die Scythe-Studio Facebook-Seite zu liken, um Benachrichtigungen über neue Beiträge zu erhalten!

 

Haftungsausschluss

Scythe-Studio übernimmt keine Verantwortung für unangemessene oder böswillige Nutzung der vorgestellten Lösungen und des Codes. Der gesamte Inhalt dieses Beitrags dient ausschließlich Bildungszwecken.

 

Scythe-Studio - Blog Redactors

Scythe Studio Blog Redactors

Brauchen Sie Qt QML-Entwicklungsdienste?

service partner

Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.

Entdecken Sie unsere Fähigkeiten!

Neueste Artikel

[ 156 ]