28

私はQtで作業しており、[GO]ボタンを押すと、パッケージをネットワークに継続的に送信し、受信した情報でインターフェイスを変更する必要があります。

問題は、ボタンにがwhile(1)含まれているため、ボタンが終了しないため、インターフェイスが更新されないことです。while(){}ボタンにスレッドを作成して、そこにコードを配置しようと思いました。

私の質問は、スレッドからインターフェイスを変更するにはどうすればよいですか?(たとえば、スレッドからtextBoxを変更するにはどうすればよいですか?

4

4 に答える 4

52

Qtについて重要なことは、メインスレッドであるGUIスレッドからのみQtGUIを操作する必要があるということです。

そのため、これを行う適切な方法は、ワーカーからメインスレッドに通知することです。メインスレッドのコードは、実際にはテキストボックス、プログレスバーなどを更新します。

これを行う最良の方法は、posixスレッドの代わりにQThreadを使用し、スレッド間の通信にQtシグナルを使用することだと思います。これはあなたの労働者、次の代替品になりますthread_func

class WorkerThread : public QThread {
    void run() {
        while(1) {
             // ... hard work
             // Now want to notify main thread:
             emit progressChanged("Some info");
        }
    }
    // Define signal:
    signals:
    void progressChanged(QString info);
};

ウィジェットで、.hのシグナルと同じプロトタイプでスロットを定義します。

class MyWidget : public QWidget {
    // Your gui code

    // Define slot:
    public slots:
    void onProgressChanged(QString info);
};

.cppで、この関数を実装します。

void MyWidget::onProgressChanged(QString info) {
    // Processing code
    textBox->setText("Latest info: " + info);
}

スレッドを生成したい場所で(ボタンをクリックすると):

void MyWidget::startWorkInAThread() {
    // Create an instance of your woker
    WorkerThread *workerThread = new WorkerThread;
    // Connect our signal and slot
    connect(workerThread, SIGNAL(progressChanged(QString)),
                          SLOT(onProgressChanged(QString)));
    // Setup callback for cleanup when it finishes
    connect(workerThread, SIGNAL(finished()),
            workerThread, SLOT(deleteLater()));
    // Run, Forest, run!
    workerThread->start(); // This invokes WorkerThread::run in a new thread
}

シグナルとスロットを接続した後emit progressChanged(...)、ワーカースレッドでスロットを発行するとメインスレッドにメッセージが送信され、メインスレッドはそのシグナルに接続されているスロットを呼び出しonProgressChangedます。

PS私はまだコードをテストしていないので、どこかが間違っている場合は、遠慮なく編集を提案してください

于 2013-01-27T09:45:38.733 に答える
1

したがって、メカニズムは、スレッド内からウィジェットを変更できないというものです。そうしないと、アプリケーションが次のようなエラーでクラッシュします。

QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Segmentation fault

これを回避するには、次のように、スレッド化された作業をクラスにカプセル化する必要があります。

class RunThread:public QThread{
  Q_OBJECT
 public:
  void run();

 signals:
  void resultReady(QString Input);
};

run()には、実行したいすべての作業が含まれています。

親クラスには、データを生成する呼び出し関数とQTウィジェットの更新関数があります。

class DevTab:public QWidget{
public:
  void ThreadedRunCommand();
  void DisplayData(QString Input);
...
}

次に、スレッドを呼び出すために、いくつかのスロットを接続します。

void DevTab::ThreadedRunCommand(){
  RunThread *workerThread = new RunThread();
  connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
  connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
  workerThread->start();  
}

接続関数は4つのパラメーターを取り、パラメーター1は原因クラス、パラメーター2はそのクラス内のシグナルです。パラメータ3はコールバック関数のクラスであり、パラメータ4はクラス内のコールバック関数です。

次に、子スレッドにデータを生成する関数があります。

void RunThread::run(){
  QString Output="Hello world";
  while(1){
    emit resultReady(Output);
    sleep(5);
  }
}

次に、ウィジェットを更新するためのコールバックが親関数にあります。

void DevTab::UpdateScreen(QString Input){
  DevTab::OutputLogs->append(Input);
}

その後、実行すると、スレッドでemitマクロが呼び出されるたびに、親のウィジェットが更新されます。接続関数が適切に構成されている場合、出力されたパラメーターを自動的に取得し、コールバック関数の入力パラメーターに格納します。

これがどのように機能するか:

  1. クラスを初期化します
  2. emit通常の方法ではスレッドからデータを返すことができないため、スレッドの終了時に発生する処理と、「返された」別名データを処理するためのスロットを設定します。
  3. ->start()次に、呼び出し(QThreadにハードコードされている)を使用してスレッドを実行し、 QT.run()はクラス内のハードコードされた名前memberfunctionを探します
  4. emitresultReadyマクロが子スレッドで呼び出されるたびに、QStringデータが、スレッド間で行き詰まった共有データ領域に隠されます。
  5. QTは、resultReadyがトリガーされたことを検出し、関数UpdateScreen(QString)に、run()から発行されたQStringを親スレッドの実際の関数パラメーターとして受け入れるように通知します。
  6. これは、emitキーワードがトリガーされるたびに繰り返されます。

基本的に、connect()関数は、データが前後に移動できるように、子スレッドと親スレッドの間のインターフェイスです。

注: resultReady()を定義する必要はありません。QT内部に存在するマクロのように考えてください。

于 2021-03-12T01:32:44.390 に答える
0

invokeMethod()またはSignals and slotメカニズムを使用できます。基本的に、シグナルを送信する方法やSLOTでそれを受信する方法など、多くの例があります。しかし、InvokeMethodは興味深いようです。

以下は例で、スレッドからラベルのテキストを変更する方法を示しています。

//file1.cpp

QObject *obj = NULL; //global 
QLabel *label = new QLabel("test");
obj = label;   //Keep this as global and assign this once in constructor.

次に、WorkerThreadで次のように実行できます。

//file2.cpp(つまり、スレッド)

extern QObject *obj;
void workerThread::run()
{
     for(int i = 0; i<10 ;i++
     {
         QMetaObject::invokeMethod(obj, "setText",
                                Q_ARG(QString,QString::number(i)));
     }
     emit finished();
}
于 2016-05-27T09:51:46.880 に答える
-4

スレッド関数へのポインターを渡してスレッドを開始します(posixでは、スレッド関数には署名void *(thread_func)(void *)があり、Windowsでも同じです)-そして、ポインターを自分のデータに完全に自由に送信できます(structまたは何か)そしてこれをスレッド関数から使用します(適切な型へのポインタをキャストします)。まあ、メモリ管理は完全に行う必要があります(したがって、メモリをリークしたり、スレッドからすでに解放されたメモリを使用したりすることはありません)が、これは別の問題です

于 2013-01-27T09:26:19.323 に答える