コンセプト
シグナルとスロットは、Qt フレームワークの優れたメカニズムの 1 つです。いわゆる信号スロットは、実際にはオブザーバー モードです。イベントが発生すると、たとえばボタンがクリックされたことを検出し、信号を発します。この種の送信には目的がなく、放送に似ています。オブジェクトがこの信号に興味がある場合、接続関数を使用します。これは、オブジェクトが処理する信号を独自の関数 (スロットと呼ばれる) の 1 つにバインドして信号を処理することを意味します。つまり、信号が送信されると、接続されているスロット関数が自動的にコールバックされます。これはオブザーバー パターンに似ています。つまり、対象のイベントが発生すると、操作が自動的にトリガーされます。
6.1. システムに付属する信号とスロット
connect() 関数
次に、小さな関数を完成させます。上でボタンの作成方法を学習しましたが、まだボタンの機能を反映していません。ボタンの最大の機能は、クリック後に何かをトリガーすることです。たとえば、ボタンをクリックしたとき, 現在のウィンドウを閉じます。では、Qt でそのような関数を実装するにはどうすればよいでしょうか?
実際、これは 2 行のコードだけでは実現できません。次のコードを見てみましょう。
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
1行目は以前学習した閉じるボタンの作成で、2行目は核となる信号スロットの使い方です。
connect() 関数の最もよく使用される一般形式は次のとおりです。
connect(sender, signal, receiver, slot);
パラメータの説明:sender
シグナルを送信するオブジェクト、signal
送信オブジェクトによって送信されるシグナル、receiver
シグナルを受信するオブジェクト、slot
スロット関数 (受信オブジェクトがシグナルを受信した後に呼び出す必要がある関数) )
信号信号とスロット機能スロットを見つける
システムに付属の信号とスロットを見つけるにはどうすればよいですか? これには、ヘルプ ドキュメントを使用する必要があります。ヘルプ ドキュメント (たとえば、上のボタンのクリック信号) で、ヘルプ ドキュメントに「QPushButton」と入力します。 「Contents.」の意味でキーワード シグナルを検索できますが、見つからないことがわかりました。
現時点では、おそらくこのシグナルは親クラスによって継承されていると考えるべきで、その親クラス QAbstractButton でキーワードを見つけることができます。シグナルのインデックスをクリックして、システムに付属する次のシグナルを見つけます。
スロット関数は、キーワードがスロットであることを除いて、信号と同じ方法で検索されます。
例: ボタンをクリック -> ウィンドウを閉じる
まず独自のボタンを作成します。
//创建一个自己的按钮对象
MyPushButton *myBtn=new MyPushButton;
myBtn->setText("我自己的按钮");
myBtn->move(200,0);
myBtn->setParent(this);//设置到对象树中,窗口释放的时候会被自动释放
次に、connect 関数を使用してボタンを閉じたウィンドウにリンクします。
//需求 点击我的按钮--->关闭窗口
//参数1 信号的发送者 参数2 发送的信号(函数的地址) 参数3 信号的接收者 参数四 处理的槽函数
connect(myBtn,&MyPushButton::clicked,this,&myWidget::close);
//connect(myBtn,&QPushButton::clicked,this,&QWidget::close);
例証します:
- MyPushButton は、QPushButton からパブリックに継承されたカスタム クラスであり、オブジェクトの作成とリリースを容易にするために、そのコンストラクターとデストラクターに qDebug ステートメントを追加しました。
- 送信者は作成したボタンの名前であり
myBtn
、受信者はこのウィンドウを表します。this
- 送信シグナルとスロット関数については、カスタム
MyPushButton
クラスが から継承しているためQPushbutton
、ここでのスコープ制限は両方を使用できることです。
6.2. カスタム信号とスロット (パラメーターなし)
connect() を使用すると、システムが提供するシグナルとスロットに接続できます。ただし、Qt のシグナルとスロットのメカニズムでは、システムが提供するパーツを使用するだけでなく、独自のシグナルとスロットを設計することもできます。
Qt を使用してシグナルとスロットを見てみましょう。
教師と生徒のクラスを作成します。
最終的には次のような効果を達成したいと考えています。
//Teacher类 老师类
//Student类 学生类
//下课后,老师触发一个信号,饿了,学生响应信号,请客吃饭
次に、学生クラスと教師クラスを作成しましょう。
ファイル名を右クリックし、 を選択しadd new
、C++ クラスを選択して続行します。
Teacher
次に、クラスに名前を付け、 から継承することを選択しますQObject
が、ボタンなどを実行していないので、 から継承する必要はなくQWidget
、最上位クラスから直接継承できます。
[次へ] を選択して直接完了します。Student
クラスの作成は上記と同じです。
カスタム信号 (パラメーターなし)
まずTeacher
新しいクラスを作成し、ヘッダー ファイルでカスタム信号を宣言します - Hungryhungry
signals:
//自定义信号 写道signals下
//返回值是 void,只需要声明,不需要实现
//可以有参数,可以发生重载
void hungry();
};
カスタムスロット機能(パラメータなし)
次に、新しいStudent
クラスを作成し、ヘッダー ファイルでスロット関数を宣言します。よろしくお願いします。treat
//早期Qt版本 必须写到public slots,高级版本可以写到 public或者全局下
//返回值是void,需要声明,也需要实现
//可以有参数,可以发生重载
void treat();
signals:
学生の cpp ファイルにスロット関数を実装します。
void Student:: treat()
{
qDebug()<<"请老师吃饭";
}
パブリックメソッドを実装します(パラメータなし)
ウィンドウWidget
ヘッダーファイルに、オブジェクトTeacher
へのポインターを作成します。Student
private:
Ui::Widget *ui;
Teacher *zt;
Student *st;
void classIsOver();
};
ウィンドウ cpp に public メソッドを実装して class を終了しますClassIsOver()
。このメソッドの呼び出しにより、教師がお腹が空いているという信号がトリガーされ、対応するスロット関数が生徒を処理します。
void Widget::classIsOver()
{
//下函数,调用后,出发老师饿了的信号
emit zt->hungry();
}
emit
カスタム信号。
カスタム信号とスロットを接続します (パラメーターなし)
ウィンドウ cpp で教師と生徒のオブジェクトを作成し、connect 関数を使用して接続し、クラスから抜け出す関数の最後を呼び出してシグナルをトリガーします。
//创建一个老师对象
this->zt=new Teacher(this);
//创建一个学生对象
this->st=new Student(this);
//老师饿了,学生请客的连接
connect(zt,&Teacher::hungry,st,&Student::treat);
//调用下课函数
classIsOver();
結果は次のとおりです。
「先生を食事に招待する」をデバッグしました
6.3. カスタム信号とスロット (オーバーロード -> パラメーターあり)
オーバーロードされたカスタム信号とスロット (パラメーター付き)
シグナルのリロード:
signals:
//自定义信号 写道signals下
//返回值是 void,只需要声明,不需要实现
//可以有参数,可以发生重载
void hungry();
void hungry(QString foodName);
void Student:: treat()
{
qDebug()<<"请老师吃饭";
}
void Student::treat(QString foodName)
{
qDebug()<<"请老师吃饭,老师要吃:"<<foodName;
}
オーバーロードされたスロット関数:
//早期Qt版本 必须写到public slots,高级版本可以写到 public或者全局下
//返回值是void,需要声明,也需要实现
//可以有参数,可以发生重载
void treat();
void treat(QString foodName);
signals:
クラス外の関数をオーバーロードします (パラメーターを使用)
void Widget::classIsOver()
{
//下函数,调用后,出发老师饿了的信号
//emit zt->hungry();
emit zt->hungry("宫保鸡丁");
}
オーバーロードされた信号とスロットを接続します (パラメーターを使用)
同じ名前のカスタム信号とカスタム スロットが 2 つあるため、直接接続するとエラーが報告されるため、関数ポインターを使用して関数アドレスを指定してから接続する必要があります。
//连接带参数的 信号和槽
//指针->地址
//函数指针->函数地址
void(Teacher::*teacherSignal)(QString foodName)=&Teacher::hungry;
void(Student::*studentSlot)(QString foodName)=&Student::treat;
connect(zt,teacherSignal,st,studentSlot);
classIsOver();
変換出力結果(QString->char *)
結果は次のとおりです。
ご覧のとおり、結果は正しく出力されていますが、結果には引用符が含まれているため、引用符を削除したい場合は型変換を行う必要があります。
QByteArray (.toUtf8())
まずそれを に変換し、次に に変換できますchar *
。ここで、QByteArray は Qt のバイト配列です。
void Student:: treat()
{
qDebug()<<"请老师吃饭";
}
void Student::treat(QString foodName)
{
//QString->char * 先转成QByteArray (.toUtf8()) 再转char * (.data)
qDebug()<<"请老师吃饭,老师要吃:"<<foodName.toUtf8().data();
}
予防:
信号スロットをカスタマイズする際の注意事項:
- 送信者と受信者は両方とも QObject のサブクラスである必要があります (もちろん、スロット関数がグローバル関数、ラムダ式などで受信者を必要としない場合は除きます)。
- シグナル関数とスロット関数の戻り値は void です
- シグナルは宣言するだけでよく、実装する必要はありません
- スロット関数を宣言して実装する必要がある
- スロット関数は通常のメンバー関数であり、メンバー関数であるため、public、private、protected の影響を受けます。
- 正しい位置に信号を送信するには、emit を使用します。
- connect() 関数を使用して信号とスロットを接続します。
- 任意のメンバー関数、静的関数、グローバル関数、ラムダ式をスロット関数として使用できます。
- 信号とスロットでは、信号とスロットのパラメータが一致している必要があり、いわゆる一貫性とは、パラメータの種類が一致していることを意味します。
- 信号とスロットのパラメータが一致しない場合、スロット関数が信号よりも少ないパラメータを持つことができますが、その場合でも、スロット関数のパラメータの順序は信号の最初の数個と一致している必要があります。これは、スロット関数の信号からのデータを無視することを選択できるためです (つまり、スロット関数のパラメーターが信号よりも少ないため)。
6.4. 信号スロットの拡張
- 信号は複数のスロットに接続可能
この場合、スロットは次々に呼び出されますが、呼び出される順序は未定義です。
- 複数の信号をスロットに接続可能
このスロットは、信号が発信されるたびに呼び出されます。
- 信号は別の信号に接続できます
最初の信号が発信されると、2 番目の信号が発信されます。それ以外の場合、この信号信号形式と信号スロット形式の間に違いはありません。
- スロットのリンクを解除できる
オブジェクトが削除されると、Qt はこのオブジェクトに接続されているすべてのスロットを自動的にキャンセルするため、この状況は頻繁には発生しません。
- 信号スロットは切り離すことができます
信号スロットは、disconnectキーワードを使用して切断できます。
- ラムダ式の使用
Qt 5 を使用する場合、Qt 5 をサポートできるすべてのコンパイラーはラムダ式をサポートします。
信号とスロットを接続する場合、ラムダ式を使用してスロット関数を処理できます。ラムダ式とは何かについては後ほど詳しく紹介します。
クラスからの終了をトリガーするボタンを設定します (パラメータ付き)
ClassIsOver
まず関数をコメントアウトし、ボタン button を作成し、ClassIsOver
ボタン -> ClassIsOver 関数 -> ハングリーシグナル -> スロット関数の処理を通じて関数に接続します。
//连接带参数的 信号和槽
//指针->地址
//函数指针->函数地址
void(Teacher::*teacherSignal)(QString foodName)=&Teacher::hungry;
void(Student::*studentSlot)(QString foodName)=&Student::treat;
connect(zt,teacherSignal,st,studentSlot);
//classIsOver();
//点击一个下课的按钮,再触发下课
QPushButton *btn=new QPushButton("下课",this);
//重置窗口大小
this->resize(600,400);
//点击下课按钮,触发下课(有参)
connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);
結果は次のとおりです。
ボタンをクリックしてスロット機能をトリガーします
信号接続信号 (パラメータなし)
ClassIsOver
今回はスロット関数をトリガーする関数は使用せず、ボタン -> TeacherSignal2 ->studentSlot2 の効果を実現するために 2 つの connect 関数を使用します。
まず前のボタン スロット関数をコメント アウトし、パラメータなしで 2 つのスロット接続を確立します。
//点击下课按钮,触发下课(有参)
// connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);
//无参信号和槽连接
void(Teacher::*teacherSignal2)(void)=&Teacher::hungry;
void(Student::*studentSlot2)(void)=&Student::treat;
connect(zt,teacherSignal2,st,studentSlot2);
//信号连接信号 按钮-->老师信号-->学生treat函数
connect(btn,&QPushButton::clicked,zt,teacherSignal2);
//拓展
//1、信号可以连接多个信号
//2、一个信号可以连接多个槽函数
//3、多个信号可以连接同一个槽函数
//4、信号和槽函数的参数,必须一一对应
//5、信号和槽的参数个数 是不是要一致?-信号的参数个数 可以多余槽函数的参数个数
効果は次のとおりです。
ボタンを押して食べると、パラメーターなしでスロット機能がトリガーされます
切断信号
複数の信号が相互に接続されており、個別に切断できます
//断开信号 老师信号-->学生treat函数
//disconnect(zt,teacherSignal2,st,studentSlot2);
6.5. Qt4版の書き込み方法
ここでQ は2 つのマクロ SIGNAL と SLOT を使用して、2 つの関数名を文字列に変換します。connect() 関数のシグナルとスロットの両方が文字列を受け入れることに注意してください。接続が失敗すると、Qt4 はコンパイル エラーを起こさず (すべてが文字列であり、コンパイル時に文字列が一致するかどうかをチェックしないため)、実行時のエラー。これにより、間違いなくプログラムの不安定性が増大します。
Qt5 は構文的には Qt4 と完全に互換性がありますが、その逆は不可能です。
//Qt4版本以前的信号和槽连接方式
//利用Qt4信号槽 连接无参版本
//Qt4版本 底层SIGNAL("hungry") SLOT("treat")
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));
//Qt4版本优点:参数直观,缺点 : 类型不做检测
//Qt5以上 支持Qt4的版本写法,反之不支持
6.6. ラムダ式
C++11 のラムダ式は、プログラミングを簡素化するために匿名関数オブジェクトを定義および作成するために使用されます。まず、ラムダ式の基本構成を見てみましょう。
[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}
[capture](parameters) mutable ->return-type
{
statement
}
1. 関数オブジェクトのパラメータ。
[] はLambda の始まりを示します。この部分は必ず存在し、省略できません。関数オブジェクトのパラメータは、コンパイラによって自動生成された関数オブジェクト クラスのコンストラクターに渡されます。関数オブジェクトのパラメーターは、Lambda が定義されるまで (Lambda が配置されているクラスの変数を含む)、Lambda のスコープ内で表示されるローカル変数のみを使用できます。関数オブジェクトのパラメータには次の形式があります。
- ヌル。関数オブジェクトのパラメータは使用されません。
- =。Lambda のスコープ内で表示されるすべてのローカル変数 (Lambda が配置されているクラスのローカル変数を含む) は関数本体で使用でき、値によって渡されます(コンパイラーがすべてのローカル変数を値によって自動的に渡すのと同じです)。
[=] (){
btn->setText("aaaa");
}();
- &。Lambda のスコープ内で表示されるすべてのローカル変数 (Lambda が配置されているクラスのローカル変数を含む) は関数本体で使用でき、参照によって渡されます(コンパイラーがすべてのローカル変数を参照によって自動的に渡すのと同じです)。
[&] (){
btn->setText("aaaa");
}();
- これ。Lambda が配置されているクラスのメンバー変数は、関数本体内で使用できます。
- a.を値で渡します。値で渡す場合、関数はデフォルトで const であるため、渡された のコピーを関数本体内で変更することはできません。に渡された のコピーを変更するには、可変修飾子を追加します。
[btn] (){
btn->setText("aaaa");
//bt2->setText("aaaa");//btn2看不到
}();
- &a. を参照によって渡します。
- a、&b。a を値で渡し、b を参照で渡します。
- =、&a、&b。参照によって渡される a と b を除き、他のすべてのパラメーターは値によって渡されます。
- &、a、b。値によって渡される a と b を除き、他のパラメータは参照によって渡されます。
2. 関数パラメータをオーバーロードする演算子。
オーバーロードされた () 演算子のパラメーターを識別します。パラメーターがない場合、この部分は省略できます。パラメータは値によって渡すこともできます (例: (a,b))、または参照によって渡すこともできます (例: (&a,&b))。
3. 識別子は変更できます。
mutable ステートメントの場合、この部分は省略できます。関数オブジェクトのパラメーターを値で渡す場合、可変修飾子を追加した後、値で渡されたコピーを変更できます (変更できるのは値自体ではなく、コピーであることに注意してください)。
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn->move(200,200);
myBtn2->move(100,100);
int m = 10;
//值传递默认是只读的 加上mutable关键字,可以修改值传递拷贝的值,而不是本体
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable {
m = 100 + 10;
qDebug() << m;
});
connect(myBtn2,&QPushButton::clicked,this,[=] () {
qDebug() << m;
});
qDebug() << m;
4. 関数の戻り値。
->戻り値の型、関数の戻り値の型を識別します。戻り値が void の場合、または関数本体に戻り値が 1 つしかない場合 (このとき、コンパイラーは戻り値の型を自動的に推測できます)、この部分は省略可能です。
int ret=[]()->int {return 1000;}();
qDebug()<<"ret = "<<ret;
5. 関数本体です。
{} は関数の実装を識別します。この部分は省略できませんが、関数本体は空であってもかまいません。
6. ラムダ式はウィンドウを閉じるボタンを実装します
//利用lambda表达式 实现点击按钮 关闭窗口
QPushButton *btn2=new QPushButton;
btn2->setText("关闭");
btn2->move(100,0);
btn2->setParent(this);
connect(btn2,&QPushButton::clicked,this,[=](){
this->close();
emit zt->hungry("宫保鸡丁");
//btn2->setText("aaaa");//点击变为aaaa
});
//Lambda表达式,最常用,[](){}