alle 1000 ms eine Zeile in eine Textdatei schreiben

Alles rund um die Programmierung mit Qt
Antworten
Luftballon
Beiträge: 29
Registriert: 10. Mai 2009 20:03

alle 1000 ms eine Zeile in eine Textdatei schreiben

Beitrag von Luftballon »

Hallo zusammen!

Ich bin Anfänger in Sachen C++/Qt-Programmierung, finde dies jedoch sehr spannend und versuche es an einem kleineren Programm zu erlernen.

Dieses Programm soll ersteinmal alle 1000 ms einen Datensatz (die Sekundenangabe der aktuellen Zeit) in eine Textdatei schreiben. Dabei soll jeder Datensatz eine Zeile in der Textdatei einnehmen. Anbei als Attachment meine ersten Gehversuche.

Drei Punkte funktionieren aktuell nicht:

1.
Das Einfügen von der Sekundenangabe in die Textdatei ( void Thread::threadAction() )

2.
Der repetitive Aufruf und/oder das repetitive Schreiben in die Textdatei ( void Thread::threadAction() )

3.
Das Anhalten des Threads (void Thread::threadStop() )

Was muss ich ändern, damit diese drei Punkte funktionieren? Über eure Hilfe würde ich mich sehr freuen!

Beste Grüße,
der Luftballon
Dateianhänge
Mousometer.cpp
(1.59 KiB) 183-mal heruntergeladen
Thread.cpp
(592 Bytes) 189-mal heruntergeladen
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Drei Punkte funktionieren aktuell nicht:
Das sagt uns leider wenig... versuche, so genaue Fehlerbeschreibungen wie möglich zu geben, wie z.B.

"Die Datei "test.txt" wird erstellt, aber leider wird dort immer nur eine einzelne Zeile reingeschrieben".
Dann könnten wir die Antwort schreiben: "Du öffnest die Datei ja auch beim jeden Durchgang neu, daher wird immer der alte Inhalt überschrieben. Entweder du öffnest die Datei nur einmal (beim Threadstart) und hast im Slot nur noch "file.write(...)" oder du öffnest die Datei so, dass die Daten angehängt werden (siehe Flags http://doc.trolltech.com/4.5/qiodevice. ... eFlag-enum).

Oder du hättest schreiben können:
"Die Datei wird nicht erstellt".
Dann würden wir schreiben: "Zeig mal die Headerdatei, vermutlich funktioniert der Slot wegen fehlendem "Q_OBJECT" nicht, aber das müsstest du anhand der Konsolenausgabe oder durch die Kontrolle des Returnwertes von connect(...) auch selbst rausgefunden haben..."

Zum Thema Thread:
* Warum überhaupt ein Thread? Der Timer könnte doch in jeder normalen QObject-Klasse laufen..
* "quit()" muss vermutlich aus dem Thread aufgerufen werden. Du musst dem Thread also mitteilen, er solle sich beenden. Wie, bleibt (vorerst) deiner Fantasie überlassen...

Hth...

[EDIT]
zu "seconds() funktioniert nicht": übe den Umgang mit QStrings... (lies die Doku, http://doc.trolltech.com/4.5/qstring.html#arg-10).. dann klappt das.
Troll.Soft
Beiträge: 190
Registriert: 18. Juni 2008 09:52
Wohnort: Hamburg

QTimer

Beitrag von Troll.Soft »

Hallo,
möglicherweise suchst Du QTimer. Diese Klasse hat alles was Du brauchst und ein Beispiel gibt es auch.

Code: Alles auswählen

Example for a one second (1000 millisecond) timer (from the Analog Clock example):
     QTimer *timer = new QTimer(this);
     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
     timer->start(1000);
From then on, the update() slot is called every second.
tschüß
der Trollsoft
Luftballon
Beiträge: 29
Registriert: 10. Mai 2009 20:03

Beitrag von Luftballon »

Vielen Dank für eure Antworten!

Die Datei "test.txt" wir jetzt nicht mehr bei jedem Durchgang neu geöffnet und somit klappt nun auch das Schreiben mehrerer Zeilen in diese Datei.

Das Schreiben der Sekundenangabe funktioniert allerdings nicht. Hier habe ich jetzt

Code: Alles auswählen

void Thread::timerAction() {
    QTime t = QTime::currentTime();
    QFile file("test.txt");
    file.open(QIODevice::Append | QIODevice::Text);
    file.write( file.write( QString("Sekunde = %1\n").arg(t.second() )) );
    file.close();
}
ausprobiert, aber das produziert folgende Fehlermeldung:
=============
MouseOMat/Thread.cpp:17: error: no matching function for call to `QFile::write(QString)'
MouseOMat/../../../../Qt/2009.02/qt/include/QtCore/../../src/corelib/io/qiodevice.h:123: note: candidates are: qint64 QIODevice::write(const char*, qint64)
MouseOMat/../../../../Qt/2009.02/qt/include/QtCore/../../src/corelib/io/qiodevice.h:124: note: qint64 QIODevice::write(const char*)
MouseOMat/../../../../Qt/2009.02/qt/include/QtCore/../../src/corelib/io/qiodevice.h:126: note: qint64 QIODevice::write(const QByteArray&)
=============[/code]

Einen Thread wollte ich verwenden, damit später zu diesem repetitiven Schreiben in die Textdatei noch etwas anderes parallel ausgeführt werden kann. Dieses "etwas anderes" ist das Auslesen einer USB-Gerätes, dass in unregelmäßigen Abständen wohl eine 1 senden wird. Die Einsen sollen dann gezählt und deren Anzahl pro Zeitintervall in die Textdatei geschrieben werden.

Das "quit()" habe ich jetzt an eine andere Stelle gesetzt (void Mousometer::stopMouse()).

Wenn ich das Programm starte, dann bekomme ich folgende Meldungen in der Konsole

Code: Alles auswählen

Starting MouseOMat/debug/MouseOMat.exe...
QObject: Cannot create children for a parent that is in a different thread.
(Parent is Thread(0x5217670), parent's thread is QThread(0x3e4728), current thread is Thread(0x5217670)
QObject::killTimer: timers cannot be stopped from another thread
Die 2. und 4. Zeile gibt mir das Gefühl, als würde das noch nicht 100%ig richtig sein. allerdings habe ich aktuell keine Idee was ich verändern könnte.

Über weitere Hilfe freue ich mich sehr.

Beste Grüße,
der Luftballon
Dateianhänge
Mousometer.cpp
(1.61 KiB) 172-mal heruntergeladen
Thread.cpp
(661 Bytes) 177-mal heruntergeladen
Thread.h
(351 Bytes) 165-mal heruntergeladen
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Ganz ehrlich? Ich würde dir davon abraten, als C++ sowie Qt Anfänger direkt mit der Threadprogrammierung und dem Lesen von USB Geräten anzufangen.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

no matching function for call to `QFile::write(QString)'
Schau dir an, welche write() Varianten du hast.. und danach http://doc.trolltech.com/4.5/qstring.html#toAscii

Zu "quit()".. das ist ja ein Slot des Threads. Du soltlest IMHO darauf achten, dass dieser Slot auch wirklich aus dem Kontext des Threads aufgerufen wird. Eine Variante waere daher eine Signal/Slot-"QueuedConnection" (Die Applikation "bittet" mit einem Signal den Thread, sich zu beenden. Der Thread fuehrt den Slot "quit" dann in _seinem_ Eventloop aus.). Keine Ahnung ob das ueberhaupt funktioniert, "quit" aus einem anderen Thread (wie du das tust) aufzurufen. Falls das ueberhaupt funktioniert, muesste das unbedingt noch mittels Mutex synchronisiert werden.
Threads sind nicht einfach, wie pfid schon hingewiesen hat :wink:
Luftballon
Beiträge: 29
Registriert: 10. Mai 2009 20:03

Beitrag von Luftballon »

Mit der Zeile

Code: Alles auswählen

file.write( QString("Sekunde = %1\n").arg(t.second()).toUtf8() );
funktioniert es.

Das mit dem QThread ist wirklich nicht einfach. Ich werde mir deine letzte Antwort zu diesem Thema später genauer anschauen. Was ich hier ja eigentlich möchte ist, dass
1. nach Betätigung eines QPushButton ein neuer Thread entsteht und gestartet wird und
2. nach Betätigung eines weiteren QPushButton dieser Thread wieder beendet wird

Zu sowas müsst es doch eigentlich Beispiele im Netz geben, oder versuche ich QThread so zu benutzen, wie es eigentlich nicht gedacht ist?
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

1. nach Betätigung eines QPushButton ein neuer Thread entsteht und gestartet wird und
2. nach Betätigung eines weiteren QPushButton dieser Thread wieder beendet wird
kein Problem, Beendet wird doch einfach mit:
* Signal "clicked()" des "Beenden-Buttons" mit Slot "quit" des Threads
* Signal "finished()" des Treads mit einem eigenen Slot der GUI, in welchem du die Instanz loescht (thread->deleteLater()); thread = NULL);
Luftballon
Beiträge: 29
Registriert: 10. Mai 2009 20:03

Beitrag von Luftballon »

Jetzt funktioniert es schonmal seh gut. Ich kann mit dem Button "Programm starten..." den Thread und dessen Timer starten und mit dem Button "Programm beenden..." selbige wieder beenden bzw. stoppen.

Allerdings bekomme ich in der Debug-Konsole dabei folgende Meldungen:
1. beim Starten -> "QObject: Cannot create children for a parent that is in a different thread. (Parent is Thread(0x22fde4), parent's thread is QThread(0x3e4748), current thread is Thread(0x22fde4)"
2. beim Beenden -> "QObject::killTimer: timers cannot be stopped from another thread"

Warum ist das so und wie kann ich das verbessern?

Dann habe ich noch in der Klasse die Methode "closeEvent(QCloseEvent *event)" reimplementiert (so heißt das glaube ich). Damit wollte ich eigentlich verhindern, dass das Programm geschlossen wird, wenn der Thread noch läuft. Jedoch wird diese Methode nicht aufgerufen, wenn ich das Programm schließe. Was läuft hier falsch?

Würde mich über eure Hilfe sehr freuen!

Beste Grüße,
der Luftballon

Mousometer.cpp:

Code: Alles auswählen

#include "Mousometer.h"

Mousometer::Mousometer(QWidget *parent) : QMainWindow(parent) {
    w       = new QWidget;
    layout  = new QGridLayout(w);
    spin    = new QSpinBox;
    slider  = new QSlider(Qt::Horizontal);
    label   = new QLabel("Anzahl der Durchläufe");
    button1 = new QPushButton("Programm starten...");
    button2 = new QPushButton("Programm stoppen...");
    edit    = new QTextEdit;

    edit->setFixedWidth(500);
    edit->setFixedHeight(300);

    spin->setMinimum(1);
    spin->setMaximum(100);
    slider->setMinimum(1);
    slider->setMaximum(100);

    layout->addWidget(label, 0, 0);
    layout->addWidget(slider, 0, 1);
    layout->addWidget(spin, 0, 2);
    layout->addWidget(button1, 1, 1);
    layout->addWidget(button2, 2, 1);
    layout->addWidget(edit, 3, 0, 1, 3);

    QObject::connect( spin, SIGNAL( valueChanged(int) ), slider, SLOT( setValue(int) ) );
    QObject::connect( slider, SIGNAL( valueChanged(int) ), spin, SLOT( setValue(int) ) );
    QObject::connect( button1, SIGNAL( clicked() ), this, SLOT( letMouseRun() ) );
    QObject::connect( button2, SIGNAL( clicked() ), this, SLOT( stopMouse() ) );
    QObject::connect( &thread, SIGNAL( finished() ), this, SLOT( threadReady() ) );

    QMenu *fileMenu = new QMenu(tr("&Datei"), this);
    menuBar()->addMenu(fileMenu);
    fileMenu->addAction(
        QIcon("./images/cancel.png"),
        tr("Beenden..."), qApp, SLOT(quit()),
        QKeySequence(tr("Ctrl+X", "Datei|Beenden")));

    resize(320, 200);
    setCentralWidget(w);
    setWindowTitle("Mousometer");
}

Mousometer::~Mousometer() {}

void Mousometer::letMouseRun() {
    if( thread.isRunning() ) {
        edit->moveCursor(QTextCursor::End) ;
        edit->insertPlainText(tr("QThread laeuft bereits\n"));
        return;
    }
    slider->setValue(55);
    thread.start();
        edit->moveCursor(QTextCursor::End) ;
        edit->insertPlainText(tr("QThread gestartet\n"));
}

void Mousometer::stopMouse() {
    slider->setValue(66);
    thread.timerStop();
}

void Mousometer::threadReady()  {
   edit->moveCursor(QTextCursor::End) ;
   edit->insertPlainText(tr("QThread ist fertig\n"));
}

void Mousometer::closeEvent(QCloseEvent *event) {
    qDebug() << "xxxxxxxxxxxxxxxx";
    if( thread.isRunning() ) {
        edit->moveCursor(QTextCursor::End) ;
        edit->insertPlainText(tr("QThread laeuft noch\n"));
        return;
    }
   //thread.wait();
}
Thread.cpp

Code: Alles auswählen

#include "Thread.h"
#include <QtGui>

Thread::Thread() {}

void Thread::run() {
   timer = new QTimer(this);
   QObject::connect( timer, SIGNAL(timeout()), this, SLOT(timerAction()) );
   timer->start(500);  // alle 500 ms ein Methodenaufruf
   exec();
}

void Thread::timerAction() {
    qDebug() << "i am alive!";
    QTime t = QTime::currentTime();

    QFile file("test.txt");
    file.open(QIODevice::Append | QIODevice::Text);
    file.write( QString("Sekunde = %1\n").arg(t.second()).toUtf8() );
    file.close();
}

void Thread::timerStop() {
    timer->stop();
    quit();
}
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Es mach überhaupt keinen Sinn, da einen Pointer zu nehmen. Besonders, weil du ja eh nur den Thread beenden möchtest und daher gar kein stop() notwendig wäre:

Code: Alles auswählen

void Thread::run() {
   QTimer timer;
   QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(timerAction()) );
   timer.start(500);  // alle 500 ms ein Methodenaufruf
   exec();
} 
fertig.. die erste Meldung erhälst du, weil "this" genaugenommen vom Mainthread erstellt wird und auch diesem gehört...
Die zweite, weil du versuchst, den Thread aus dem Kontext eines anderen zu steuern ("timerStop()" wird vom Mainthread her aufgerufen und wird daher auch im Mainthread ausgeführt, _nicht_ im nebenläufigen Thread!).
Ich kann mich daher nur wiederholen: Beeende den Thread nicht direkt, sondern teile dem Thread (über ein Flag ("bool terminateMe") oder besser ein SIGNAL) mit, dass er sich beenden soll.. mit einem Flag würde dann in "timerAction()" stehen "if (terminateMe) quit();"...
Antworten