Qt笔记总结(上)

简介: Qt笔记总结的上篇

一、常用控件


按钮类


QPushButton

QtoolButton

QRadioButton


item


QListWidget


容器类


QStackWidget

QWidget

QFrame


编辑类


QComboBox

QLineEdit

QTextEdit


显示类


QLabel


setOpenExternalLinks()设置为true自动打开,false要打开链接需要捕捉linkActivated()信号


//显示普通文本字符串
 QLable *label = new QLable;
label->setText(“Hello, World!”);
//显示HTML格式的字符串
 QLabel * label = new QLabel(this);
label ->setText("Hello, World");
label ->setText("<h1><a href="https://www.baidu.com">
 百度一下</a></h1>");
label ->setOpenExternalLinks(true);
 QLabel * label = new QLabel(this);
label ->setText("Hello, World");
label ->setText("<h1><a href="https://www.baidu.com">
 百度一下</a></h1>");
// label->setOpenExternalLinks(true);
 connect(label, &QLabel::linkActivated, 
 this, &MyWidget::slotOpenUrl);
//槽函数 
 void MyWidget::slotOpenUrl(const QString &link)
 {
  QDesktopServices::openUrl(QUrl(link));
 }


QProcessBar

QMessageBox


二、信号与槽


信号和槽


信号槽,实际就是观察者模式。当某个事件发生之后,它就会发出一个信号(signal)将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。当信号发出时,被连接的槽函数会自动被回调。


1、connect(sender, signal, receiver, slot);


  • sender:发送信号的对象
  • signal:发送对象发出的信号
  • receiver:接收信号的对象
  • slot:接收对象在接收到信号之后所需要调用的函数


例如:


/* &b1: 信号发出者,指针类型
 * &QPushButton::pressed:处理的信号 (&发送者的类名::信号名字)
 * this: 信号接收者
 * &MainWidget::close:槽函数,信号处理函数 (&接收的类名::槽函数名字)
 */
 connect(&b1, &QPushButton::pressed, this, &MainWidget::close);


自定义信号槽


自定义信号槽需要注意的事项


  • 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
  • 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
  • 使用 emit 在恰当的位置发送信号;
  • 使用QObject::connect()函数连接信号和槽。
  • 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数


/* 自定义槽,普通函数的用法
 * Qt5:任意的成员函数,普通全局函数,静态函数
 * 槽函数需要和信号一致(参数,返回值)
 * 由于信号都是没有返回值,所以,槽函数一定没有返回值
 */
 connect(b2, &QPushButton::released, this, &MainWidget::mySlot);


Lambda表达式


C++11中的Lambda表达式用于定义并创建匿名的函数对象


基本构成:[函数对象参数](操作符重载函数参数)mutable或exception ->返回值{函数体}


1.png


1、 函数对象参数:


[ ] 标识一个**Lambda的开始**,这部分必须存在,**不能省略**


*   空,没有任何函数对象参数

   

*   =,**值传递方式**(作用范围:所有可见的局部变量以及所在类的this)

   

*   &,**引用传递方式**(作用范围:所有可见的局部变量以及所在类的this)

   

*   this,函数体内可以可以使用**Lambda所在类**中的成员变量

   

*   a,把a按值进行传递(默认为const不可修改,可添加mutable修饰符修改)

   

*   &b,把b按引用进行传递

   

*   &,a,b,除a和b进行值传递,其他参数按引用进行传递

   

2、 操作符重载函数参数:


标识重载的()操作符的参数,没有参数时,这部分可以省略。


3、 可修改标示符:


mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。


4、错误抛出标示符:


exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)


5、函数返回值:


->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。


6、函数体:


{},标识函数的实现,这部分不能省略,但函数体可以为空。


三、Qt窗口系统


1、坐标体系


以左上角为原点,X向右增加,Y向下增加。


2.png


2、QWidget


所有窗口及窗口控件都是从QWidget直接或间接派生出来的。


  1. 对象模型


  • Qt创建对象的时候会提供一个Parent对象指针


在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)


  • QWidget是能够在屏幕上显示的一切组件的父类


QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件,也可以自己删除子对象,它们会自动从其父对象列表中删除。


  • 当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的


  • 任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。


3、QMainWindow


QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget)


3.png


  1. 菜单栏


  • 创建菜单栏,通过QMainWindow类的menubar() 函数获取主窗口菜单栏指针


QMenuBar * menuBar() const


  • 创建菜单,调用QMenu的成员函数addMenu() 来添加菜单


QAction* addMenu(QMenu * menu)
 QMenu* addMenu(const QString & title)
 QMenu* addMenu(const QIcon & icon, const QString & title)


  • 创建菜单项,调用QMenu的成员函数addAction() 来添加菜单项


QAction* activeAction() const
 QAction* addAction(const QString & text)
 QAction* addAction(const QIcon & icon, const QString & text)
 QAction* addAction(const QString & text, const QObject * receiver,
  const char * member, const QKeySequence & shortcut = 0)
 QAction* addAction(const QIcon & icon, const QString & text, 
const QObject * receiver, const char * member, 
const QKeySequence & shortcut = 0)


  1. 工具栏


  • 直接调用QMainWindow类的addToolBar() 函数获取主窗口的工具条对象,每增加一个工具条都需要调用一次该函数。
  • 插入属于工具条的动作,即在工具条上添加操作。通过QToolBar类的addAction() 函数添加。
  • 工具条是一个可移动的窗口,它的停靠区域由QToolBar的allowAreas决定,包括:


/*
 Qt::LeftToolBarArea //停靠在左侧
 Qt::RightToolBarArea //停靠在右侧
 Qt::TopToolBarArea //停靠在顶部
 Qt::BottomToolBarArea   //停靠在底部
 Qt::AllToolBarAreas //以上四个位置都可停靠
 */
/*使用setAllowedAreas()函数指定停靠区域*/
 setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
/*使用setMoveable()函数设定工具栏的可移动性*/
 setMoveable(false);//工具条不可移动, 只能停靠在初始化的位置上
 ```


  1. 状态栏


  • 派生自QWidget类,使用方法与QWidget类似,QStatusBar类常用成员函数:


//添加小部件
 void addWidget(QWidget * widget, int stretch = 0)
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch = 0)
//删除小部件
 void removeWidget(QWidget * widget)


  1. 对话框QDialog


Qt 中使用QDialog类实现对话框。


对话框分为模态对话框和非模态对话框。


  1. 模态对话框,就是会阻塞同一应用程序中其它窗口的输入。


模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分进行操作的。


  1. 与此相反的是非模态对话框,例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。


  1. 标准对话框


Qt 的内置对话框大致分为以下几类:


  • QColorDialog: 选择颜色;
  • QFileDialog: 选择文件或者目录;
  • QFontDialog: 选择字体;
  • QInputDialog: 允许用户输入一个值,并将其值返回;
  • QMessageBox: 模态对话框,用于显示信息、询问问题等;
  • QPageSetupDialog: 为打印机提供纸张相关的选项;
  • QPrintDialog: 打印机配置;
  • QPrintPreviewDialog:打印预览;
  • QProgressDialog: 显示操作过程。


  1. 自定义消息框


Qt 支持模态对话框和非模态对话框。


模态与非模态的实现:


  • 使用QDialog::exec()实现应用程序级别的模态对话框
  • 使用QDialog::open()实现窗口级别的模态对话框
  • 使用QDialog::show()实现非模态对话框。


  1. 模态对话框


下面的示例中,我们调用了exec()将对话框显示出来,因此这就是一个模态对话框。当对话框出现时,我们不能与主窗口进行任何交互,直到我们关闭了该对话框。


void MainWindow::open()
 {
  QDialog dialog;
  dialog.setWindowTitle(tr("Hello, dialog!"));
  dialog.exec();
 }


  1. 非模态对话框


下面我们试着将exec()修改为show(),看看非模态对话框:


void MainWindow::open()
 {
  QDialog dialog(this);
  dialog.setWindowTitle(tr("Hello, dialog!"));
  dialog.show();
 }


是不是事与愿违?对话框竟然一闪而过!这是因为,show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。注意,dialog 是建立在栈上的,show()函数返回,MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了。知道了原因就好改了,我们将 dialog 改成堆上建立,当然就没有这个问题了:


void MainWindow::open()
 {
  QDialog *dialog = new QDialog;
  dialog->setWindowTitle(tr("Hello, dialog!"));
  dialog->show();
 }


如果你足够细心,应该发现上面的代码是有问题的:dialog 存在内存泄露!dialog 使用 new 在堆上分配空间,却一直没有 delete。解决方案也很简单:将 MainWindow 的指针赋给 dialog 即可。还记得我们前面说过的 Qt 的对象系统吗?


不过,这样做有一个问题:如果我们的对话框不是在一个界面类中出现呢?由于QWidget的 parent 必须是QWidget指针,那就限制了我们不能将一个普通的 C++ 类指针传给 Qt 对话框。另外,如果对内存占用有严格限制的话,当我们将主窗口作为 parent 时,主窗口不关闭,对话框就不会被销毁,所以会一直占用内存。在这种情景下,我们可以设置 dialog 的WindowAttribute:


void MainWindow::open()
 {
  QDialog *dialog = new QDialog;
  dialog->setAttribute(Qt::WA_DeleteOnClose);
  dialog->setWindowTitle(tr("Hello, dialog!"));
  dialog->show();
 }


setAttribute()函数设置对话框关闭时,自动销毁对话框。


  1. 消息对话框


QMessageBox用于显示消息提示。我们一般会使用其提供的几个 static 函数:


  • 显示关于对话框。
void about(QWidget * parent, const QString & title, const QString & text)


这是一个最简单的对话框,其标题是 title,内容是 text,父窗口是 parent。对话框只有一个 OK 按钮。


  • 显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。

void aboutQt(QWidget * parent, const QString & title = QString())


  • 显示严重错误对话框。


StandardButton critical(QWidget * parent,
 const QString & title,
 const QString & text,
 StandardButtons buttons = Ok,
 StandardButton defaultButton = NoButton)


这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明其显示的按钮。默认情况下只有一个 Ok 按钮,我们可以使用StandardButtons类型指定多种按钮。


  • 与QMessageBox::critical()类似,不同之处在于这个对话框提供一个普通信息图标。


StandardButton information(QWidget * parent,
 const QString & title,
 const QString & text,
 StandardButtons buttons = Ok,
 StandardButton defaultButton = NoButton)


  • 与QMessageBox::critical()类似,不同之处在于这个对话框提供一个问号图标,并且其显示的按钮是“是”和“否”。


StandardButton question(QWidget * parent,
 const QString & title,
 const QString & text,
 StandardButtons buttons = StandardButtons( Yes | No ),
 StandardButton defaultButton = NoButton)


  • 与QMessageBox::critical()类似,不同之处在于这个对话框提供一个黄色叹号图标。


StandardButton warning(QWidget * parent,
 const QString & title,
 const QString & text,
 StandardButtons buttons = Ok,
 StandardButton defaultButton = NoButton)


  1. 标准文件对话框


QFileDialog,也就是文件对话框。


openAction = new QAction(QIcon(":/images/file-open"),
  tr("&Open..."), this);
 openAction->setShortcuts(QKeySequence::Open);
 openAction->setStatusTip(tr("Open an existing file"));
 saveAction = new QAction(QIcon(":/images/file-save"), 
 tr("&Save..."), this);
 saveAction->setShortcuts(QKeySequence::Save);
 saveAction->setStatusTip(tr("Save a new file"));
 QMenu *file = menuBar()->addMenu(tr("&File"));
 file->addAction(openAction);
 file->addAction(saveAction);
 QToolBar *toolBar = addToolBar(tr("&File"));
 toolBar->addAction(openAction);
 toolBar->addAction(saveAction);
 textEdit = new QTextEdit(this);
 setCentralWidget(textEdit);


使用connect()函数,为这两个QAction对象添加响应的动作:


connect(openAction, &QAction::triggered, 
 this, &MainWindow::openFile);
 connect(saveAction, &QAction::triggered, 
 this, &MainWindow::saveFile);


下面是最主要的openFile()和saveFile()这两个函数的代码:


//打开文件
void MainWindow::openFile()
 {
  QString path = QFileDialog::getOpenFileName(this,
  tr("Open File"), ".", tr("Text Files(*.txt)"));
  if(!path.isEmpty()) 
 {
  QFile file(path);
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) 
 {
  QMessageBox::warning(this, tr("Read File"),
  tr("Cannot open file:n%1").arg(path));
  return;
  }
  QTextStream in(&file);
  textEdit->setText(in.readAll());
  file.close();
  } 
else
 {
  QMessageBox::warning(this, tr("Path"),
  tr("You did not select any file."));
  }
 }
 //保存文件
void MainWindow::saveFile()
 {
  QString path = QFileDialog::getSaveFileName(this,
  tr("Open File"), ".", tr("Text Files(*.txt)"));
  if(!path.isEmpty()) 
 {
  QFile file(path);
  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) 
 {
  QMessageBox::warning(this, tr("Write File"),
  tr("Cannot open file:n%1").arg(path));
  return;
  }
  QTextStream out(&file);
  out << textEdit->toPlainText();
  file.close();
  } 
else
 {
  QMessageBox::warning(this, tr("Path"),
  tr("You did not select any file."));
  }
 }


QFileDialog::getOpenFileName()来获取需要打开的文件的路径。这个函数原型如下:


QString getOpenFileName(QWidget * parent = 0,
  const QString & caption = QString(),
  const QString & dir = QString(),
  const QString & filter = QString(),
  QString * selectedFilter = 0,
  Options options = 0)


六个参数分别是:


  • parent:父窗口
  • caption:对话框标题
  • dir:对话框打开时的默认目录


“.” 代表程序运行目录


“/” 代表当前盘符的根目录(特指 Windows 平台;Linux 平台当然就是根目录),这个参数也可以是平台相关的,比如“C:”等


  • filter:过滤器。


我们使用文件对话框可以浏览很多类型的文件,但是,很多时候我们仅希望打开特定类型的文件


  • selectedFilter:默认选择的过滤器
  • options:对话框的一些参数设定


比如只显示文件夹等等,它的取值是enum QFileDialog::Option,每个选项可以使用 | 运算组合起来


四、布局管理器



Qt提供了两种组件定位机制:绝对定位和布局定位。


绝对定位


绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值。


如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。


布局定位


只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。


Qt 提供的布局中以下三种是我们最常用的:


  1. QHBoxLayout:按照水平方向从左到右布局;
  2. QVBoxLayout:按照竖直方向从上到下布局;
  3. QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;


自定义控件


UI的控件和自定义控件的父类(基类)要一样


选中UI控件 -> 提升


五、消息机制和事件


事件


Qt 中所有事件类都继承于QEvent


event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)


在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如:


  1. keyPressEvent()
  2. keyReleaseEvent()
  3. mouseDoubleClickEvent()
  4. mouseMoveEvent()
  5. mousePressEvent()
  6. mouseReleaseEvent() 等。


这些函数都是 protected virtual 的,也就是说我们可以在子类中重新实现这些函数。下面来看一个例子:


class EventLabel : public QLabel
 {
 protected:
  void mouseMoveEvent(QMouseEvent *event);
  void mousePressEvent(QMouseEvent *event);
  void mouseReleaseEvent(QMouseEvent *event);
 };
void EventLabel::mouseMoveEvent(QMouseEvent *event)
 {
 this->setText(QString("
Move: (%1, %2)
 ").arg(QString::number(event->x()),
  QString::number(event->y())));
 }
void EventLabel::mousePressEvent(QMouseEvent *event)
 {
  this->setText(QString("
Press:(%1, %2)
 ").arg(QString::number(event->x()),
  QString::number(event->y())));
 }
void EventLabel::mouseReleaseEvent(QMouseEvent *event)
 {
  QString msg;
  msg.sprintf("
Release: (%d, %d)
",
  event->x(), event->y());
  this->setText(msg);
 }
 int main(int argc, char *argv[])
 {
  QApplication a(argc, argv);
  EventLabel *label = new EventLabel;
  label->setWindowTitle("MouseEvent Demo");
  label->resize(300, 200);
  label->show();
  return a.exec();
 }


EventLabel继承了QLabel,重写了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值显示在这个Label上面。


运行上面的代码,当我们点击了一下鼠标之后,label 上将显示鼠标当前坐标值。
为什么要点击鼠标之后才能在mouseMoveEvent()函数中显示鼠标坐标值?


这是因为QWidget中有一个mouseTracking属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,mouseMoveEvent()才会发出。如果mouseTracking是 false(默认即是),组件在至少一次鼠标点击之后,才能够被追踪,也就是能够发出mouseMoveEvent()事件。如果mouseTracking为 true,则mouseMoveEvent()直接可以被发出。


在构造函数里面设置label->setMouseTracking(true);即可


event()


event()函数主要用于事件的分发


例如,我们希望在一个QWidget组件中监听 tab 键的按下,那么就可以继承QWidget,并重写它的event()函数,来达到这个目的:


bool CustomWidget::event(QEvent *e)
 {
  if (e->type() == QEvent::KeyPress) {
  QKeyEvent *keyEvent = static_cast(e);
  if (keyEvent->key() == Qt::Key_Tab) {
  qDebug() << "You press tab.";
  return true;
  }
  }
  return QWidget::event(e);
 }


CustomWidget是一个普通的QWidget子类。我们重写了它的event()函数,这个函数有一个QEvent对象作为参数,也就是需要转发的事件对象。函数返回值是 bool 类型:


  1. 如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件


  1. 在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播


event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 Qt 调用我们自己实现的版本


事件过滤器


有时候,对象需要查看、甚至要拦截发送到另外对象的事件。


QObject有一个eventFilter()函数,用于建立事件过滤器。函数原型如下:


virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );


这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。


上面QWidget组件中event()函数监听 tab 键的按下修改为使用事件过滤器的版本


bool FilterObject::eventFilter(QObject *object, QEvent *event)
 {
  if (object == target && event->type() == QEvent::KeyPress) 
 {
  QKeyEvent *keyEvent = static_cast(event);
  if (keyEvent->key() == Qt::Key_Tab) {
  qDebug() << "You press tab.";
  return true;
  } else {
  return false;
  }
  }
  return false;
 }


注意:事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。


总结


Qt的事件处理,实际上是有五个层次:

  1. 重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
  2. 重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
  3. 重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
  4. 在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
  5. 重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。
目录
相关文章
|
存储 编译器 数据库
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV核心类型 Mat
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV核心类型 Mat
116 1
|
5月前
|
XML 存储 JSON
技术笔记:Qt基础之配置文件(QSettings)
技术笔记:Qt基础之配置文件(QSettings)
368 0
|
5月前
|
调度
技术笔记:QT之深入理解QThread
技术笔记:QT之深入理解QThread
54 0
|
计算机视觉
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV视频lO接口
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV视频lO接口
234 0
|
文字识别 算法 计算机视觉
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV图像处理
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV图像处理
165 1
|
Ubuntu 网络安全 Docker
[笔记]Qt5+FFMpeg+Opencv 实现实时美颜直播推流《一》基础知识以及直播服务器配置
[笔记]Qt5+FFMpeg+Opencv 实现实时美颜直播推流《一》基础知识以及直播服务器配置
167 0
|
编译器 计算机视觉
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV vs2015编译
[笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV vs2015编译
100 0
[学习][笔记] qt5 从入门到入坟:《零》vs开发qt项目
[学习][笔记] qt5 从入门到入坟:《零》vs开发qt项目
|
定位技术 图形学
[学习][笔记] qt5 从入门到入坟:<13>基于GraphicsViewFrame的贪吃蛇实现
[学习][笔记] qt5 从入门到入坟:<13>基于GraphicsViewFrame的贪吃蛇实现