暂时未有相关云产品技术能力~
前言:QWidget提供的paintEvent()函数是一个纯虚函数,继承它的子类想进行重绘时必须重新实现这个函数。引发重绘事件的情况:当窗口控件第一次显示时,系统会自动产生一个绘图事件。repaint()与update()函数被调用时。当窗口控件被其他部件遮挡,然后又显示出来时,会对隐藏的区域产生一个重绘事件。重新调整窗口大小时。paintEvent()函数是已经被高度优化过的函数,本身已经自动开启并实现了双缓冲机制,因此在Qt中重绘操作不会引起屏幕上的任何闪烁现象。repaint()函数:repaint()是最快引起重绘操作的,紧急情况下需要立刻重绘的时候可以调用repaint()。但是repaint()不能放到paintEvent()函数里面调用,或造成死循环。update()函数:update()调用之后不会立即重绘,而是将重绘事件放入主循环中,由main()主函数的事件循环(Event Loop)统一调度。update()在调用paintEvent()之前做了优化,如果update()被调用了很多次,最后这些update()会合并到一个大的重绘事件,并加入到消息队列,最后只有这个大的update()被执行一次。repaint()和update()相比,一般情况下调用update()就够了,当update()不能满足需求时,再尝试使用repaint()。在paintEvent()中实现绘图操作:绘图常用的工具有画笔类QPen、画刷类QBrush和字体类QFont等,它们都继承自QPainter类。QPainter可以绘制各种基础图形。QPen类用于绘制几何图形的边缘,由颜色、宽度、线条风格等参数组成。QBrush类是用于填充几何图形的调色板,由颜色和填充风格组成。QFont类用于文本绘制,由字体属性组成。例:void Widget::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.drawLine(10, 100, 30, 300);//画线 painter.setPen(Qt::red); painter.drawRect(10, 10, 100, 100);//红色矩形框 painter.setPen(QPen(Qt::green, 5)); painter.setBrush(Qt::blue); painter.drawEllipse(100, 10, 200, 200);//绿边蓝色填充椭圆 }其他具体用法还可以参考帮助手册查看。
前言:Qt样式表是一个可以自定义部件外观的十分强大的机制,样式表可以使用QApplication::setStyleSheet()函数将其设置到整个应用程序上,也可以使用QWidget::setStyleSheet()函数将其设置到一个指定的部件(还有它的子部件)上。如果在不同的级别都设置了样式表,那么Qt会使用所有有效地样式表,即样式表的层叠。设置样式表:1.给指定部件设置样式表://设置pushButton的背景色 ui->pushButton->setStyleSheet("background:yellow"); //设置horizontalSlider的背景颜色 ui->horizontalSlider->setStyleSheet("background:blue");通过指定部件的setStyleSheet()函数就只会对这个部件应用这个样式。2.对所有的相同部件使用相同的样式://在父窗体上调用setStyleSheet()函数 this->setStyleSheet("QPushButton{background:yellow}QSlider{background:blue}");这样,父窗体上的所有的QPushButton部件和QSlider部件的背景颜色都会为我们设置的颜色。通过这种方式可以对QListWidget中的Item进行统一样式设置:"QListWidget{border:0px;background-color:#FFFFFF;outline:0px;} QListWidget::Item {background:#FFFFFF;} QListWidget::Item:hover {background:#EEEEEE;} QListWidget::item:selected {background:#DDDDDD;} QListWidget::item:selected:!active {background:#FFFFFF;}"3.设置父部件的样式,并且子部件不继承://父部件为一个QWidet this->setObjectName("myWidget"); this->setStyleSheet(QWidget#myWidget{background:black});这里的setObjectName("myWidget")是用来设置父部件的名称,在setStyleSheet()函数中通过指定“#myWidget”指定我们设置的样式只应用在myWidget上,它里面的子部件不会继承父部件的样式。这个方法还可以和方法2一起用,即调用父部件的setStyleSheet()函数,在参数中除了可以写父部件的样式,还可以指定子部件的objectName,对子部件进行设置。4.在main函数中调用QApplication::setStyleSheet()对程序进行样式设置:直接在main函数中调用:qApp->setStyleSheet();参数为部件的样式,这种方法可以实现调用qss文件,实现样式设置。QFile qss(":/qss/style.qss"); qss.open(QFile::ReadOnly); qApp->setStyleSheet(qss.readAll()); qss.close();style.qss文件中的设置如图:这样,整个程序中的QScrollBar都将会上图设置的样式,这个方法适合进行样式统一,减少代码量。Qt样式表语法:Qt样式表的术语和语法规则与HTML CSS基本相同。1.样式规则:样式表包含了一系列的样式规则,每个样式规则由选择器(selector)和声明(declaration)组成。选择器指定了受该规则影响的部件;声明指定了这个部件上要设置的属性;例:QPushButton{color:red}QPushButton是选择器;{color:red}是声明,其中,color是属性,red是属性的值。一些选择器可以指定相同的声明,选择器中间用逗号隔开。例如:QPushButton,QLineEdit,QComboBox{color:red}样式规则的声明部分是一些 “属性:值” 对组成的列表,它们包含在大括号中,使用分号隔开。例如:QPushButton{color:red;background-color:white}可以在 Qt Style Sheets Reference 关键字对应的帮助文档中的 List of Properties 一项中查看Qt样式表支持的所有属性。2.子控件:对一些复杂的部件修改样式,可能需要访问它们的子控件,比如QComboBox的下拉按钮、QSpinBox的向上和向下箭头等。选择器可以包含子控件来对部件的特定子控件设置样式。例如:QComboBox::drop-down{image:url(dropdown.png)}在 Qt Style Sheets Reference 关键字对应的帮助文档中的 List of Stylabel Widgets 一项中列出了所有可以使用的样式表来自定义样式的Qt部件, List of Sub-Controls 一项中列出了所有可用的子控件。3.伪状态:选择器可以包含伪状态来限制规则在部件的指定状态上的应用。伪状态出现在选择器之后,用冒号隔开。例如:QPushButton:hover{color:white};表明当鼠标悬停在QPushButton上时,颜色为白色。伪状态可以用感叹号来表示否定。例如:QRadioButton:!hover{color:red};表明当鼠标没有悬停在QRadioButton上时,颜色为红色。伪状态可以多个连用,达到逻辑与的效果。例如:QCheckBox:hover:checked{color:white};表明当鼠标悬停在一个被选中的QCheckBox部件上时颜色才为白色。伪状态可以用逗号表示逻辑或操作。例如:QCheckBox:hove, QCheckBox:checked{color:white};表明当鼠标悬停在QCheckBox上时为白色,或者当QCheckBox为选中状态时为白色。伪状态可以和子控件联合使用。例如:QComboBox::drop-down:hover{image:url(dropdown.png)};表明当鼠标悬停在QComboBox的下拉按钮上时,下拉按钮的图片为dropdown.png。4.冲突解决:当几个样式规则对相同的属性指定了不同的值时就会产生冲突。例如:QPushButton#okButton{color:gray}; QPushButton{color:red};这时,okButton的color属性就产生了冲突。解决冲突的原则是:特殊的选择器优先。有伪状态比没有伪状态优先。部件自己的样式表优先于任何继承的样式表,父部件的样式表优先于祖先的样式表。所以okButton最后为灰色。5.层叠:样式表可以设置在QApplication上、父部件上或者子部件上。部件有效地样式表是通过部件祖先的样式表和QApplication上的样式表合并得到的。当发生冲突时,部件自己的样式表优先于任何继承的样式表,同样的,父部件的样式表优先于祖先的样式表。6.继承:当使用Qt样式表时,部件并不会自动从父部件继承字体和颜色(color)设置。例如,一个QPushButton包含在一个QGroupBox中,这里对QGroupBox设置颜色为红色,但是不对QPushButton设置样式表。这时,QPushButton会使用系统颜色,而不会继承QGroupBox的颜色。有些属性是有继承性的,有些属性是没有继承性的。
前言:Qt中的每个部件都包含一个调色板(QPalette),它们使用各自的调色板来绘制它们自身,这样可以使用户界面更容易配置,也更容易保持一致。QPalette通过使用setColor()函数进行颜色设置:void QPalette::setColor(ColorRole role, const QColor &color );void QPalette::setColor(ColorGroup group, ColorRole role, const QColor &color );颜色组(ColorGroup):调色板QPalette类包括了部件各种状态的颜色组。一个调色板包含3种状态:激活(Active)、非激活(Inactive)和失效(Disabled)。激活颜色组QPalette::Active,用于获得键盘焦点的窗口;非激活颜色组QPalette::Inactive,用于其他没有获得键盘焦点的窗口;失效颜色组QPalette::Disabled,用于因为一些原因而不可用的部件(不是窗口)。要改变一个应用程序的调色板,可以先使用QApplication::palette()函数来获取其调色板,然后进行更改,最后使用QApplication::setPalette()函数来使用该调色板。更改了应用程序的调色板会影响到该程序的所有窗口部件。如果想要只改变一个部件的调色板,则可以调用该部件的palette()和setPalette()函数,这样只会影响该部件及其子部件。使用方法://设置spinBox(选值框)不可用 ui->spinBox->setDisabled(true); //获取spinBox的调色板 QPalette palette = ui->spinBox->palette(); //设置spinBox不可用时的背景色为蓝色 palette.setColor(QPalette::Disabled, QPalette::Base, Qt::blue); //spinBox使用修改后的调色板 ui->spinBox->setPalette(palette);颜色角色(ColorRole):设置调色板颜色时,可以使用setColor()函数,这个函数需要指定颜色角色。在QPalette中,颜色角色用来指定该颜色所起的作用,如背景颜色或是文本颜色等。QPalette::Window,一般的背景颜色;QPalette::WindowText,一般的前景颜色;QPalette::Base,主要作为输入部件(如QLineEdit)的背景色,也可用作QComboBox的下拉列表的背景颜色或者QToolBar的Handle颜色,一般是白色或者其他浅色;QPalette::Text,和Base一起使用,作为前景色;QPalette::AlternateBase,在交替颜色的视图中作为交替背景色;QPalette::ToolTipBase,作为QToolTip和QWhatsThis的背景色;QPalette::ToolTipText,作为QToolTip和QWhatsThis的前景色;QPalette::Button,按钮部件背景色;QPalette::ButtonText,按钮部件前景色;QPalette::BrightText,一种与深色对比度较大的文本颜色,一般用于当Text或者WindowText的对比度较差时。使用方法://获取pushButton的调色板 QPalette palette = ui->pushButton->palette(); //设置pushButton按钮文本颜色为红色 palette.setColor(QPalette::ButtonText, Qt::red); //设置pushButton按钮背景颜色为绿色 palette.setColor(QPalette::Button, Qt::green); //pushButton使用修改后的调色板 ui->pushButton->setPalette(palette);
前言:Qt提供了一些全局的模板函数,这些函数在<QtAlgorithms>头文件中,是一些可以使用在容器上的十分常用的算法函数。包括复制、比较、查找、排序等。我们可以在任何提供了STL风格迭代器的容器类上使用这些算法,包括QList、QLinkedList、QVector、QMap和QHash。如果目标平台上可以使用STL,那么可以使用STL的算法来代替Qt的这些算法,因为STL提供了更多的算法,而Qt只是提供了其中最重要的一些算法。查看算法都有哪些可以进入到头文件中去查看。常用算法:QString List list; list<<"one"<<"two"<<"three"; QVector\<QStringn> vect(3);std::copy() //对容器项目进行复制将list中所有项目复制到vect中。std::copy(list.begin(), list.end(), vect.begin());std::equal() //对容器项目进行比较从list的开始到结束的所有项目与vect的开始及其后面的等数量的项目进行比较,全部相同则返回true。bool ret = std::equal(list.begin(), list.end(), vect.begin());std::find() //对容器项目进行查找从list中查找“two”,返回第一个对应的值的迭代器,如果没有找到则返回end()。QList<QStirng>::iterator iter = std::find(list.begin(), list.end(), "two");std::fill() //对容器项目进行填充将list中的所有项目填充为“eleven”。std::fill(list.begin(), list.end(), "eleven");std::count() //对容器项目进行查找个数查找6的个数。QList<int> list1; list1<<3<<3<<6<<6<<6<<8; int count = std::count(list1.begin(), list1.end(), 6);std::lower_bound() //对升序容器项目进行查找返回第一个出现5的位置,如果没有5,则返回5应该在的位置,list被查找的范围中的项目必须是升序QList<int> list1; list1<<3<<3<<6<<6<<6<<8; QList<int>::iterator iter = std::lower_bound(list1.begin(), list1.end(), 5); list1.insert(iter, 5); //结果3,3,5,6,6,6,8std::sort() //使用快速排序对容器项目进行升序排序使用快速排序算法对list2进行升序排序,排序后两个12的位置不确定。QList<int> list2; list2<<33<<12<<68<<6<<12; std::sort(list2.begin(), list2.end()); //结果6,12,12,33,68std::stable_sort() //使用一种稳定排序算法对容器项目进行升序排序使用一种稳定排序算法对list2进行升序排序,排序前在前面的12,排序后依然在前面。QList<int> list2; list2<<33<<12<<68<<6<<12; std::stable_sort(list2.begin(), list2.end()); //结果6,12,12,33,68qsort() //反向排序QList<int> list2; list2<<33<<12<<68<<6<<12; qSort(list2.begin(), list2.end(), std::greater<int>()); //结果68,33,12,12,6std::swap() //交换两个变量的值int a = 3; int b = 4; std::swap(a, b); //此时a=4,b=3其他算法:<QtGlobal>头文件中也提供了一些函数来实现一些经常使用的功能:qAbs() //获取绝对值qBound() //获取数值边界qMax() //获取两个数中的最大值qMin() //获取两个数中的最小值qRound() //返回一个浮点数接近的整数值
前言:第一次使用纯Qt Creator进行项目的开发,通过SVN将代码down下来以后,用Qt Creator打开项目,发现项目加载、项目编译时出现了很多问题,在此记录一下一些问题的解决办法,以后还会补充。问题一:Qt Creator打开项目时停留在reading project状态的问题。在Qt Creator中打开我们想要启动的项目,这时项目需要一个加载的过程,但是发现项目一直停留在“reading project”的状态,在Qt Creator中的表现为,右下角的进度框一直停留在“reading project xxx”。这个问题产生的原因是,在使用别人的Qt项目文件时,Qt的.user文件保存了以前的项目信息,需要删除才能正常运行。解决办法: 删除项目目录下的所有user文件,然后重新打开项目。问题二:Qt工程迁移到其他电脑上,编译运行不通过,编译路径仍显示为原电脑上的编译路经。当我们将电脑上的Qt工程拷贝到另一台电脑运行时,由于工程路径发生改变,导致在编译过程中报错,错误提示xxx路径错误,或找不到xxx路径。前提是我们的工程本身在原电脑上编译运行是没有问题的。这个问题产生的原因是,编译路径发生改变,我们没有更新编译路径。解决办法:将.pro和.user文件删除,重新编译。如果方法一试过之后还是不行,就将项目目录下的“build-untitled-Desktop_Qt_5_5_1_MSVC2013_32bit-Debug”和“build-untitled-Desktop_Qt_5_5_1_MSVC2013_32bit-Release”文件夹删除,重新编译运行。问题三:Qt Creator在设计中改变了页面布局,编译运行后没有生效。当我们在修改项目的页面布局时,有时候直接在设计模块进行修改,这时编译运行后的效果保持修改之前的效果,我们的修改没有生效。这个问题产生的原因是,项目编译没有将我们的修改重新进行编译。解决办法:点击菜单栏的构件,选择“清理项目”,清理完成后选择“构建项目”。
前言:信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征。在使用信号和槽时,一个信号可以关联到多个槽上,多个信号也可以关联到同一个槽上,一个信号还可以关联到另一个信号上。如果是一个信号关联多个槽,这些槽会一个接一个地执行,执行顺序与关联顺序相同。之前遇到一个面试题:如果同一个信号和同一个槽函数进行了多次连接(connect),是否会造成崩溃?答案是不会,如果同一个信号和同一个槽函数进行多次连接,那么信号在发出后,会多次执行槽函数。信号和槽详解:声明一个信号需要使用signals关键字,在signals前面不能添加public、private、protected等限定符。信号默认是public的,可以在任何地方进行发射(emit),但是建议只在定义该信号的类及其子类中进行发射改信号。信号只用声明,不需要实现。而且,信号没有返回值,只能是void类型的。只有QObject类及其子类派生的类才可以使用信号和槽机制。使用信号和槽还必须在类声明的最开始处添加Q_OBJECT宏。槽就是普通的C++函数,可以像一般函数一样使用。声明槽要使用slots关键字,一个槽可以是private、public或者protected类型的,也可以被声明为虚函数。槽函数中的参数的类型和信号参数的类型要相对应,且不能比信号的参数多。connect详解:第一个参数为发射信号的对象。第二个参数为要发射的信号。第三个参数为接收信号的对象。第四个参数为要执行的槽。第五个参数type为关联的方式。Qt::AutoConnection: 自动关联,默认值。如果receiver于发射信号在同一线程,则该类型表示Qt::DirectConnection;否则,该类型表示Qt::QueuedConnection。在信号被发射时决定表示那种类型。Qt::DirectConnection: 直接关联。发射完信号后立即调用槽函数,只有槽函数执行完成返回后,发射信号后面的代码才会执行。Qt::QueuedConnection: 队列关联。当控制返回receiver所在线程的事件循环后再执行槽函数,无论槽函数是否执行,发射信号后面的代码都会立即执行。Qt::BlockingQueuedConnection: 阻塞队列关联。类似Qt::QueuedConnection,不过,信号线程会一直阻塞,知道槽函数返回。当receiver存在于信号线程时不能使用该类型,不然程序会死锁。Qt::UniqueConnection: 唯一关联。这是一个标志,可以结合其他几种连接类型,使用按位或操作。这时两个对象间的相同的信号和槽只能有唯一的关联。使用这个类型主要是为了防止重复关联。Qt5之前的形式: connect(dlg, SIGNAL(funcSignal(int)), this, SLOT(funcSlot(int)));Qt5加入的一种形式: connect(dlg, &MyDialog::funcSignal, this, &Widget::funcSlot);使用Qt5的这种形式的一个好处就是可以再编译时进行检查,信号或槽的拼写错误、槽函数参数数目多于信号的参数数目等错误都会在编译时发现。另外,这种形式还支持C++11中的Lambda表达式,可以在关联时直接编写信号发射后要执行的代码,例如:connect(dlg, &MyDialog::funcSignal, [=](int value){ui->label->setText(QString::number(value))});信号和槽的自动关联:槽函数:on_"部件的objectName"_"信号名称"。以这种方式命名的槽函数可以直接和信号关联,不用再使用connect()函数。connectSlotsByName()函数是用来支持信号和槽自动关联的,因为setupUi()函数中自动调用了connectSlotsByName()函数,所以要使用自动关联的部件的定义都要放在setupUi()函数调用之前,且必须使用setObjectName()指定它们的objectName,只有这样才能正常使用自动关联。断开关联 disconnect:断开与一个对象多有信号的所有关联:disconnect(myObject, 0, 0, 0); 等价于myObject->disconnect();断开与一个指定信号的所有关联:disconnect(myObject, SIGNAL(mySignal()), 0, 0);等价于myObject->disconnect(SIGNAL(mySignal()));断开与一个指定的receiver的所有关联:disconnect(myObject, 0, myReceiver, 0); 等价于myObject->disconnect(myReceiver);断开一个指定信号和槽的关联:disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot())); 等价于myObject->disconnect(SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));还等价于disconnect(myConnection);//myConnection是进行关联时connect()的返回值。disconnect也可以使用基于函数指针的重载形式,及Qt5后添加的不需要SIGNAL()和SLOT()宏的形式。关于信号槽的高级应用:Qt提供了 QObject::sender() 函数来返回发送该信号的对象的指针。有时存在多个信号关联到同一个槽上的情况,此时如果想在槽函数中对某个信号进行特殊处理,就可以通过QObject::sender()来获取信号的对象指针,来进行判断是否是我们需要特殊处理的对象。如果在多个信号关联到同一个槽上,而在该槽中需要对每一个信号进行不同的处理,可以使用QSignalMapper类。QSignalMapper被叫做信号映射器,可以实现对多个相同部件的相同信号进行映射,为其添加字符串或者数值参数,然后再发射出去。QSignalMapper使用举例:QSignalMapper *signalMapper = new QSignalMapper(this); for (i = 0; i < 5; i++) { QPushButton *button = new QPushButton(this); connect(button, SIGNAL(clicked()), signalMapper, SLOT(map())); signalMapper->setMapping(button, i); } connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(handle(int))); 然后槽中可以这样写: switch(i) //i表示是哪个button发生了clicked信号 { //自定义操作 }
环境:Qt Creator5.5.1- sizeHint:大小提示sizeHint属性保存了部件的建议大小,对于不同的部件,默认拥有不同的sizeHint;同一部件的sizeHint也有可能不同,大小随着部件内容的变化而变化。- minimumSizeHint:最小大小提示minimumSizeHint保存了一个建议的最小大小提示,一般在布局中起作用。- sizePolicy:大小策略sizePolicy保存了部件的默认布局行为,在水平和垂直两个方向分别起作用,控制着部件在布局管理器中的大小变化行为。说明部件在布局管理中的缩放方式。取值有:Fixed:固定的;部件无法伸缩,它的大小只能是sizeHint()的值。Minimum:sizeHint()给部件设置一个最小值,部件最小不能小于这个值,但是部件可以被拉伸。Maximum:sizeHint()给部件设置一个最大值,部件最大不能大于这个值,但是部件可以被压缩。Preferred:优先的;sizeHint()提供给部件一个最佳大小,但是可以改变,可以拉伸或压缩。Expanding:扩大的;sizeHint()提供给部件一个合适大小,部件可以被压缩,但是部件更倾向于被拉伸来获得更大的大小。MinimumExpanding:sizeHint()提供给部件的大小是最小值,部件倾向于被拉伸来获得更大的大小。Ignord:sizeHint()的值被忽略,部件将尽可能的被拉伸来获取更大的大小。sizePolicy(大小策略)与sizeHint(大小提示)的值是有关系的。举例:在Qt Desiner中拖一个Spacer出来,设置Spacer的sizeHint的宽度为200,但是界面上的Spacer的宽度没有达到200。可以看到这时它的sizeType属性设置的是Expanding。如果将它更改为Fixed,这样界面上的Spacer马上变宽了,现在它的实际高度才是sizeHint的高度。- stretch factor:伸缩因子stretch factor是用来设置部件间的比例的。图中widget_title是一个水平布局的QWidget,其中有四个部件:所以在它的属性栏中的layoutStretch属性中就有四个数字,“0,0,0,0”这四个数字代表了四个部件的宽度比例为1:1:1:1,我们可以修改部件之间的比例关系,选中比例值,将值改为:“2,1,1,1”,这时,第一个部件就是其他三个部件的2倍宽度。
前言:程序中经常用到鼠标事件,根据不同的鼠标事件实现不同的功能。在Qt中,QMouseEvent类表示鼠标事件。通常通过重定义部件的鼠标事件处理函数的方式来进行一些自定义的操作。QWheelEvent类来表示鼠标滚轮事件,主要用来获取滚轮滚动的方向和距离。鼠标事件:我们通常使用的鼠标事件有:void mousePressEvent(QMouseEvent *event); //鼠标按键按下void mouseReleaseEvent(QMouseEvent *event); //鼠标按键抬起void mouseDoubleClickedEvent(QMouseEvent *event); //鼠标按键双击void mouseMouveEvent(QMouseEvent *event); //鼠标移动void wheelEvent(QWheelEvent *event); //鼠标滚轮滚动在QWidget中的定义如图:鼠标移动事件:鼠标的移动事件mouseMouveEvent()默认是在鼠标按键按下时移动鼠标的时候才会产生;如果不想按下鼠标也可以获取到鼠标移动事件的话,就需要在构造函数中添加:setMouseTracking(true);//设置鼠标跟踪设置鼠标形状:1.设置鼠标为系统提供的形状:QCursor cursor; cursor.setShape(Qt::OpenHandCursor);//小手掌形状 setCursor(cursor); //或者使用:QApplication::setOverriedCursor(cursor);//使鼠标指针暂时改变形状Qt中提供了常用的鼠标指针的形状,可以在帮助中通过Qt::CursorShape关键字查看。2.设置鼠标为自定义形状:QCursor cursor("./image/logo.png"); setCursor(cursor); //或者使用:QApplication::setOverrideCursor(cursor);//使鼠标指针暂时改变形状如果使用了QApplication::setOverriedCursor(cursor);来暂时改变鼠标指针的形状,那么就需要在恢复鼠标形状时使用QApplication::restoreOverrideCursor();Q_UNUSED():如果在函数中没有用到函数传进来的参数的话,比如说在void mousePressEvent(QMouseEvent *event)函数体中,我们没有使用到event这个参数,这样在编译程序的时候会出现警告,但是这不会影响程序的编译运行,如果不想出现这样的警告信息,就可以在函数内第一句加上Q_UNUSED(xxx);xxx表示不使用的参数。这样就不会出现警告了。滚轮事件:QWheelEvent()类的delta()函数返回了滚轮移动的距离。每当滚轮转动一下,默认是15度,这时调用QWheelEvent()::delta()返回的值就是15*8=120。转动方向为向外,返回正值;转动方向为向内,返回负值。所以可以通过这个函数的返回值的正负来判断滚轮的方向。实现鼠标拖动窗口移动:void Widget::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) // 鼠标左键 { m_isMouseLeftDown = true; m_dragPos = event->globalPos() - pos(); //获取指针位置和窗口位置的差值 } } void Widget::mouseMoveEvent(QMouseEvent *event) { if(event->buttons() & Qt::LeftButton)//见注: { move(event->globalPos() - m_dragPos); event->accept(); //事件处理函数“接收”了这个事件,不要再向父部件传递; } }注:因为鼠标移动时会检测所有按下的按键,而这时使用QMouseEvent的button()函数无法获取是哪个按键按下,只能使用buttons()函数,所以要用buttons()和Qt::LeftButton进行按位与运算来判断是否是鼠标左键按下。实现滚轮放大或缩小编辑器中的内容:void Widget::wheelEvent(QWheelEvent *event) { if(event->delta() > 0) { ui->textEdit->zoomIn();//放大 } else { ui->textEdit->zoomOut();//缩小 } }
前言:Qt提供的事件过滤器是由两个函数组成的用来实现在一个部件中监控自己或其他多个部件的事件的一个操作。这两个函数分别是installEventFilter()和eventFilter(),都是QObject类中的函数。使用方法:1. 对需要监控的部件安装过滤器,例如:ui->textEdit->installEventFilter(this);//为编辑部件在本窗口上安装事件过滤器要对一个部件使用事件过滤器,就要先使用其的installEventFilter()函数为其安装事件过滤器,这个函数的参数表明了监视对象。参数为this表明要在本窗口/部件中监视textEdit的事件。2. 在监视部件中重新实现eventFilter()函数:bool eventFilter(QObject *obj, QEvent *event);bool Widget::eventFilter(QObject *obj, QEvent event) { if(obj == ui->textEdit) { if(event->type == QEvent::Wheel){ return true; } else{ return false; } } else{ return QWidget::eventFilter(obj, event); } }obj为发生事件的对象,event为事件。如果要对一个特定的事件进行处理,而且不希望它在后面的传递过程中再被处理,那么就返回true,否则返回false。如果不需要处理这个事件,那么久返回这个事件,交给上级部件处理。为什么要使用过滤器:有时候,我们需要监听多个部件的事件,如果不使用过滤器,那么就得分别子类化各个部件,然后重新实现它们对应的各个事件处理函数,这样做就会很麻烦,为了避免造成这种麻烦,直接将这些部件的事件在父窗口中进行监听并做相应的处理,就会很方便。发送事件:Qt中除了可以监听事件,也提供了发送一个事件的功能:bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event);或void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority);用过MFC的童鞋看到这两个函数应该会联想到SendMessage和PostMessage吧,它们确实是类似的。sendEvent和postEvent的区别:sendEvent()会立即处理给定的事件;而postEvent()则会将事件放到等待调度队列中,当下一次Qt的主事件循环运行时才会处理它。sendEvent()中的QEvent队形参数在事件发送完成后无法自动删除,所以需要在栈上创建QEvent对象;而postEvent()中的QEvent对象参数必须在堆上进行创建(例如使用new),当事件被发送后事件队列会自动删除它。发送一个事件:QKeyEvent myEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModify); qApp->sendEvent(ui->spinBox, &myEvent);//发送键盘事件到spinbox部件这里的qApp是QApplication对象的全局指针,每一个应用程序只能使用一个QApplication对象,等价于使用QApplication::sendEvent()。Qt还可以使用自定义的事件,这个事件需要继承QEvent类。
前言:在Qt中,事件是被作为一个对象的,继承自QEvent类。事件和信号不是一个概念,比如说单击一下界面上的按钮,这时产生一个鼠标事件(QMouseEvent),这个事件不是按钮产生的,但是因为按钮被按下,所以按钮会发射一个单击信号(clicked()),这个信号是按钮产生的。事件的流程是: 发生一个事件时,就会产生一个QEvent对象,这个QEvent对象会传递给当前部件的event()函数。如果当前部件没有安装事件过滤器eventFilter,则会被event函数放到相应的事件函数(xxxEvent)中去。事件主要分为两类:在与用户交互时发生(比如按下鼠标:mousePressEvent);系统自动发生(比如定时器事件:timerEvent);QObject子类实例都可以接收和处理事件。事件的处理:当我们接收到一个事件,需要对事件进行处理时,一般会有下面这几种方法:方法一:重新实现事件函数。 例如重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。这时最常用的一种方法,不过只能用来处理特定部件的特定事件。例如: void paintEvent(QPaintEvent *event);方法二:重新实现notify()函数。 这个函数的功能非常强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是它一次只能处理一个事件。方法三:向QApplication对象上安装事件过滤器。 因为一个程序只有一个QApplication对象,所以这样实现的功能与使用notify()函数是相同的,优点是可以同时处理多个事件。方法四:重新实现event()函数。 QObject类的event()函数可以在事件到达默认的事件处理函数之前获得该事件。例如: bool event(QEvent *event);方法五:在对象上安装事件过滤器。 使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。例如: bool eventFilter(QObject *obj, QEvent *event);最常用的方法是方法一、其次是方法五。因为方法二需要继承自QApplication类,方法三要使用一个全局的事件过滤器,这会减缓事件的传递,所以这两个方法虽然很强大,但是很少用。事件的传递:一个多层级的窗口中,事件是先传递给指定窗口部件的,也就是先传递给获得焦点的窗口部件。但是如果该部件的事件函数中调用了ignore()函数忽略掉这个事件,那么这个事件会传递给这个部件的父部件。重新实现事件处理函数时,一定要调用父类的相应事件处理函数来实现默认操作。例如:在paintEvent()函数末尾要调用:QWidget::paintEvent(event);在event()函数末尾要调用:return QWidget::event(event);在eventFilter()函数末尾调用:return QWidget::eventFilter(obj, event);事件的传递顺序是: 先是事件过滤器,然后是焦点部件的event()函数,最后是焦点部件的事件处理函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数。
对于模态和非模态对话框的理解:QDialog类是所有对话框窗口类的基类。对话框窗口是一个经常用来完成短小任务或者和用户进行简单交互的顶层窗口。按照运行对话框时是否还可以和改程序的其他窗口进行交互,对话框常被分成模态对话框和非模态对话框。模态对话框是在对话框关闭之前,不能再与同一个应用程序的其他窗口进行交互。非模态对话框是可以与同意程序中的窗口交互的。创建对话框的方法:我们创建对话框时一般会用到三种方式:1. 使用new的方式创建一个非模态对话框:QDialog *dialog = new QDialog(this); dialog->show();通过new一个QDialog对象指针,指定窗口的父窗口,调用show()函数显示出来。这样是可以正常显示的,只不过这个窗口是非模态的,他不会阻止这段代码之后的代码的正常运行。并且因为指定了父窗口,所以不需要使用delete()来释放该对象,在父窗口销毁时,会自动释放所有子窗口。2. 定义临时变量创建一个模态对话框:QDialog dialog(this); dialog.exec();通过这种方式启动一个模态对话框,在程序走到这里时,会弹出对话框,并且这段代码之后的代码将不会执行,只有在这个对话框关闭之后才会继续执行。3. 使用new的方式创建一个模态对话框:QDialog *dialog = new QDialog(this); dialog->setModal(true); dialog->show();通过这种方式启动的对话框也是模态的,它和第一种方法的区别是调用了setModal()函数。这时虽然是模态对话框,但是在这段代码之后的代码也是可以正常执行的。是因为在调用show()函数后会立即将控制权交给调用者,程序可以继续往下执行。而调用exec()函数时,只有当对话框关闭时才会返回。setModal()函数:与setModal()函数功能相似的还有一个setWindowModality()函数,它有一个参数来设置模态对话框要阻塞的窗口类型,这个参数可以是:Qt::NonModal()//不阻塞任何窗口,就是非模态Qt::WindowModal()//阻塞它的父窗口、所有祖先窗口以及他们的子窗口Qt::ApplicationModal()//阻塞整个应用程序的所有窗口setModal()函数默认设置的是Qt::ApplicationModal。
环境:Qt Creator 5.5.1前言:写Qt项目,必须要了解Qt的.pro文件。.pro文件是项目文件,其中包含了项目相关信息。直接在Qt Creator中打开项目,双击.pro文件就可以打开它看到里面的信息。.pro文件:第1~5行:是注释信息。 说明了这个文件生成的时间。第7行:表明了这个项目使用的模块有哪些。core模块包含了Qt的核心功能,其他所有模块都依赖于这个模块;gui模块提供了窗口系统集成、时间处理、OpenGL ES集成、2D图形、基本图像、字体和文本等功能。这两个模块是使用qmake工具来构建项目时被默认包含进来的,所以在手动编写项目的时候也不需要添加这两个模块就可以进行编译。如果项目中需要用到其他模块,例如network、webkitwidgets、Serial Port等,只需要在这后面接着写就可以。或者另起一行在下面写“QT += network”也是可以的。第9行:添加widgets模块。 Qt Widgets模块中提供了经典的桌面用户界面的UI元素集合,简单来说,所有C++程序用户界面部件都在该模块中。这行代码的意思是,如果Qt主版本大于4(也就是说当前使用的是Qt5或者更高版本),则需要添加widgets模块。因为程序中使用的几个类都包含在widgets模块中,所以这里需要添加这行代码。其实,这里直接使用“QT += widgets”也是可以的,但是为了保持与Qt4的兼容性,建议使用图上这种方式。第11行:是生成的exe文件的名字。 默认是项目名称,也可以在这里改为别的名称。第12行:表示使用app模板。 表明这是个应用程序。第15、18和20行:分别是工程中包含的源文件、头文件、界面文件。 这里不需要手动进行设置,在Qt Creator中添加文件时,会自动添加到这里。第22行:添加应用程序图标。 就是标题栏左上角的图标以及生成的exe的图标。这些文件都使用了相对路径,因为都在项目目录中,所以只写了文件名。第24行:添加资源文件。 如果项目中用到了资源文件,如qss、图片文件等,都可以作为资源文件添加到项目中,这时就需要使用到添加资源文件,会自动生成,也可以自己手动修改。
前言:从今天开始,因为工作需要,开发环境从VS2017换成了Qt Creator5.5,之前用过Qt Creator,但是用到的都是一些简单的功能,现在需要系统学习一下使用Qt Creator了。菜单栏:菜单栏一共有8个菜单选项,包含了常用的功能菜单:文件菜单:包含了新建、打开、关闭项目和文件、打印和退出等基本功能。编辑菜单:包含了撤销、剪切、复制、查找和选择编码等功能,高级菜单(Advanced)中还有标示空白符、折叠代码、改变字体大小和使用vim风格编辑等功能。构建菜单:包含构建和运行项目等相关功能。调试菜单:包含调试运行项目等相关功能。Analyze菜单:包含QML分析器、Valgrind内存和功能分析器等相关功能。工具菜单:提供了快速定位菜单、外部工具菜单等。这里的选项菜单中包含了Qt Creator各个方面的设置选项:环境设置、文本编辑器设置、帮助设置、构建和运行设置、调试器设置和版本控制设置等。在环境设置的Interface页面可以将主题Theme设置为Classic,这样就可以使用以前经典Qt Creator主题了。控件菜单:包含了设置窗口布局的一些菜单,如全屏显示和隐藏边栏功能等。帮助菜单:包含Qt帮助、Qt Creator版本信息、报告bug和插件管理等菜单。模式选择器:欢迎模式:主要提供了一些功能的快捷入口,如打开帮助教程、打开示例程序、打开项目、新建项目、快速打开以前的项目和会话、联网查看Qt官方论坛和博客等。示例页面显示了Qt自带的大量示例程序,并提供了搜索栏从而实现快速查找;教程页面提供了一些视频教程资源,但是是英文的。编辑模式:主要用来查看和编辑程序代码,管理项目文件。Qt Creator中的编辑器具有关键字特殊颜色显示、代码自动补全、声明定义间快捷切换、函数原型提示、F1键快速打开相关帮助和全项目中进行查找等功能。也可以在“工具->选项”菜单中对编辑器进行设置。设计模式:整合了Qt Designer的功能。可以设计图形界面,进行部件属性设置、信号和槽设置、布局设置等操作。调试模式:支持设置断点、单步调试和远程调试等功能,包含局部变量和监视器、断点、线程以及快照等查看窗口。项目模式:包含对特定项目的构建设置、运行设置、编辑器设置、代码风格设置和依赖关系等页面。构建设置中可以对项目的版本、使用的Qt版本和编译步骤进行设置;编辑器设置中可以设置文件的默认编码;在代码风格设置中可以设置自己的代码风格。帮助模式:在帮助模式中将Qt助手整合了进来,包含目录、索引、查找和书签等几个导航模式,可以在帮助中查看Qt和Qt Creator的各方面信息。构建套件选择器:构建套件选择器包含了目标选择器(Target selector)、运行按钮(Run)、调试按钮(Debug)和构建按钮(Building)四个图标。目标选择器用来选择要构建哪个项目、使用哪个Qt库、这对于多个Qt库的项目很有用。还可以选择编译项目的debug版本或是release版本。运行按钮可以实现项目的构建和运行;调试按钮可以进入调试模式,开始调试程序;构建按钮完成项目的构建。定位器:使用定位器来快速定位项目、文件、类、方法、帮助文档以及文件系统。可以使用过滤器来更加准确地定位要查找的结果。输出窗格:输出窗格包含了问题、搜索结果(Search Results)、应用程序输出、编译输出、Debugger Console、概要信息、版本控制(Version Control)7个选项,它们分别对应一个输出窗口,响应的快捷键依次是Alt+数字1~7。问题窗口显示程序编译时的错误和警告信息;搜索结果窗口显示执行了搜索操作后的结果信息;应用程序窗口显示应用程序运行过程中输出的所有信息;编译输出窗口显示程序编译过程输出的相关信息;版本控制窗口显示版本控制的相关输出信息。
环境:Qt Creator 5.5.1第一步:选择项目模板。打开Qt Creator,选择“文件->新建文件或项目”菜单项,在选择模板页面选择Application中的Qt Widgets Application项,然后单击Choose按钮。第二步:输入项目信息。在“项目介绍和位置”页面输入项目的名称,点击右侧的“浏览”按钮选择源码路径。如果选中了“设为默认的项目路径”,那么以后创建的项目会默认使用该目录。单击“下一步”进入下一个页面。注意:项目名称和路径都不能出现中文。第三步:选择构建套件。这里显示的构建套件为Desktop Qt 5.5.1 MSVC2013 32bit。点击右侧详情,可以看到默认为Debug版本和Release版本分别设置了两个不同的目录。点击“下一步”进入下一个页面。第四步:输入类信息。在“类信息”页面中创建一个自定义类。这里设定类名为HelloWorld,基类选择QDialog,使用这个类可以生成一个对话框界面。这时头文件、源文件和界面文件都会自动生成。点击“下一步”进入下一个页面。第五步:设置项目管理。在这里可以看到这个项目的汇总信息,这个项目中都包含哪些文件。这里直接点击“完成”按钮完成项目创建。此时项目已经完成创建,直接进入到了编辑模式。界面右边是编辑器,可以阅读和编辑代码。左边侧边栏罗列了项目中的所有文件。其中,HelloWorld.pro文件是该项目的项目文件,其中包含了项目的相关信息。可以在项目路径下双击此文件启动该项目。双击项目列表中界面文件分类下的helloword.ui文件,此时进入到设计模式。1. 主设计区: 主设计区用来设计界面及各个部件的属性。2. 部件列表窗口: 部件窗口罗列了各种常用的标准部件,可以使用鼠标将这些部件拖入主设计区中。3. 对象查看器: 对象查看器列出了界面上所有的对象名称和父类,而且以树形结构显示各个部件之间的关系。可以在这里单击对象来选中该部件。4. 属性编辑器: 属性编辑器显示了各个部件的常用属性信息,可以在这里对空间属性进行设置。这些属性按照从祖先继承的属性、从父类继承的属性和自己的属性的顺序进行了分类。5. 动作编辑器与信号和槽编辑器: 这两个编辑器可以对相应的对象内容进行编辑。6. 常用功能图标: 上面侧边栏中的前4个分别是窗口部件编辑模式、信号/槽编辑模式、伙伴编辑模式和Tab顺序编辑模式。后面的几个图标用来实现添加布局管理器以及调整大小等功能。
环境:VS2017+Qt5.14.2前言:根据开发需要配置开发环境,如果需要用到VS2017+Qt5.14.2的环境配置,就可以看一下这篇文章,同理VS2010+Qt5.9也类似。并且VS2017也可以配Qt5.9,看自己的需求。安装:1.安装VS2017;VS2017的安装包可以在网上找到,安装需要花费一些时间。2.安装Qt5.14.2;直接在Qt官网中下载Qt的安装包,在安装的时候一般会把Qt的路径单独设置在D:盘,这样在环境配置、共同开发、添加Qt库时比较方便。Qt的官方网址:www.qt.io/3.安装插件:分别安装好VS和Qt之后,他们两个现在还互相不认识,不能一起工作,现在他们还是两个独立的软件。我们想要在VS中可以使用Qt,就需要安装一个VS2017下的QT5插件,用于VS下的QT编程使用,安装后,VS中会出现QT vs Tools选项。这个插件也是在Qt的官网中的,这里贴上下载地址:download.qt.io/official_re…配置:打开VS2017,在工具栏中找到QT vs Tools,点击后选择Qt Versions,这里是配置Qt的版本,点击添加按钮,然后选择Qt的路径D:\Qt\Qt5.14.2\5.14.2\msvc2017_64就可以了。点击工具栏中的 工具,点击 扩展和更新,选择Qt Visual Studio Tools, 查看右侧版本,版本应该为2.5.1,然后将“自动更新此扩展”前面的对勾去掉,我们不让它自动更新。注:如果它自动更新的话,会对我们的工程产生影响。比如我们的工程创建的时候是2.5.1,后来变成了2.8.1,程序可能突然就运行不起来的,这时就需要我们重新安装插件2.5.1版本才可以。Qt程序打包:生成Release的exe文件。把生成的.exe文件放在一个文件夹中(要打包的文件夹)。点击电脑的开始,找到Qt的生成工具“Qt 5.14.2 for UWP x64(MSVC 2017 64-bit)”。执行命令:windeployqt xxx 其中XXX为.exe的路径+文件名。成功执行后,目录下会打包进一堆库文件,然后把文件夹直接压缩给别的电脑就可以用了。
包含Socket的头文件:#include <winsock.h>想要使用关于Socket的相关类型(SOCKET, SOCKADDR_IN)和函数,需要先引用Socket的头文件。创建套接字成员变量:SOCKET m_UdpSocket; //UDP通信套接字 SOCKADDR_IN m_UdpSocketAddr; //UDP通信套接字地址Socket通信需要用到SOCKET类型和SOCKADDR_IN类型,所谓Socket,是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。它是网络环境中进程间通信的API(应用程序编程接口)。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另一台主机的Socket中,使对方能够接收到这段信息。Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。启动套接字:WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { return FALSE; }WSAStartup,即WSA(Windows Sockets Asynchronous, Windows异步套接字)的启动命令。WSAStartup必须是应用程序或DLL调用的第一个Windows Socket函数。它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。创建套接字:m_UdpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (m_UdpSocket == INVALID_SOCKET) { WSACleanup();//释放套接字资源 return FALSE; }在任何类型的通信开始之前,都必须先创建出套接字。创建套接字的参数1为:套接字类型,分为基于文件的套接字(AF_UNIX)和面向网络的套接字(AF_INET);参数2为:连接方式,分为面向连接的套接字(SOCK_STREAM,指TCP)和面向无连接的套接字(SOCK_DGRAM,指UDP);参数3为:指定协议,常用的协议有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_TIPC等,他们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。我们这里指定的是网络套接字,UDP连接,UDP传输协议。设置广播权限:BOOL bBroadcast = TRUE; setsockopt(m_UdpSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(bBroadcast)); m_UdpSocketAddr.sin_family = AF_INET; m_UdpSocketAddr.sin_port = htons(atoi(m_strPort.c_str()));这里调用setsockopt是希望发送UDP数据报的时候,socket发送的数据具有广播特性。setsockopt的使用还有很多种,可以参考这篇文章:setsockopt用法详解设置IP为广播地址:int iPos = 0; std::vector<std::string> vStringIP = splitString(m_strLocalIP, ".", true); std::string ipCStr = vStringIP[0] + "." + vStringIP[1] + "." + vStringIP[2] + ".255";//L"255.255.255.255"; m_UdpSocketAddr.sin_addr.S_un.S_addr = inet_addr(ipCStr.c_str()); //设置ip为广播地址 /*inet_addr(ip_char);*/ 全网地址:htonl(INADDR_BROADCAST)逻辑是获取本机的IP地址,然后将IP地址的最后一个数改为255,这个IP地址就是这个网段的广播地址。splitString()函数是我自己写的一个函数,主要用来将IP地址的四个部分存在一个vector中。启动广播线程:m_UdpThreadInfo.pThis = this; m_UdpThreadInfo.isExit = FALSE; m_UdpThreadInfo.threadHandle = CreateThread(0, 0, ThreadFunction, &m_UdpThreadInfo, 0, &(m_UdpThreadInfo.threadID));我这里是将发送广播数据放在一个线程中实现,在线程中指定一个固定的间隔时间发送数据。发送广播数据:if (sendto(m_UdpSocket, data, dataSize, 0, (SOCKADDR*)&m_UdpSocketAddr, sizeof(m_UdpSocketAddr)) == SOCKET_ERROR) { return FALSE; }调用sendto()函数实现数据的发送。data为发送的数据,类型为char *,dataSize为发送的数据的大小。停止广播线程,关闭套接字:m_UdpThreadInfo.isExit = TRUE; closesocket(m_UdpSocket);//关闭套接字,并等待接收线程结束 WaitForSingleObject(m_UdpThreadInfo.threadHandle, 3000); CloseHandle(m_UdpThreadInfo.threadHandle); m_UdpThreadInfo.threadHandle = NULL;线程函数是一个while()循环,突出线程时,需要将参数isExit置为TRUE。调用closesocket()函数实现关闭套接字。WaitForSingleObject()函数是一种Windows API函数,用来检测线程句柄的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在指定的挂起时间(ms)内,线程所等待的对象变为信号状态,则函数立即返回,如果到指定时间,句柄所指向的对象还没有编程信号状态,仍然返回。它在这里的作用就是等待线程退出,但是不阻塞主线程。
前言:如果程序的资源是保存在百度云服务其中的,那我们在实现文件上传时,需要调用百度云提供给我们的接口去访问百度云服务器进行文件上传的工作。上传文件又分为简单上传、追加上传、分块上传、断点续传等。简单上传就是上传一个文件大小比较小、上传时间比较短的文件,百度云的简单上传支持5G以下。简单上传这种上传方式用户不可以再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。所以百度云提供了追加上传的功能,大小限制为0~5G。分块上传支持上传超过5G大小的文件,支持断点续传,支持流式地上传文件呢,应用于网络条件比较差,和服务器之间的连接经常断开的情况下,还有如果在上传文件之前,无法确定上传文件的大小的情况下,也应该采用分块上传的方式。断点续传是为了解决如果上传文件较大、网络不稳定或遇到程序崩溃等问题时,整个文件的上传就失败了,失败前上传的部分也就作废了,用户就需要重新上传,这样做会浪费资源,可能导致多次上传都无法完成。所以百度云提供了断点续传的方法。主要思想是多次调用分块上传,然后将数据再进行拼接,形成一个完整的文件。我们这里用到的主要是C++实现文件的简单上传,参考百度云SDK手册,手册网址为:百度云C++ SDK 简单上传实现:1. 首先在项目中添加百度云的SDK,官网有提供。2. 设置节点区域、类似账号、类似密码。baidu::bos::cppsdk::ClientOptions option; option.max_parallel = ; option.endpoint = ""; std::string ak = ""; std::string sk = ""; baidu::bos::cppsdk::Client client(ak, sk, option);以上的值都要根据自己的百度云信息进行设置。3. 上传文件。int ret = client.upload_file(bucketName, objectKey, filePath);bucketName为百度云中的存放的位置的名称。objectKey为要上传到百度云时创建的文件名filePath为文件在本机存放的地址。返回值为0代表上传成功。
环境:vs2017+Qt5.14.2效果图:准备工作:效果图中的可以转动的仪表盘效果分为三个部分:背景图(就是带去掉中间白色原点,去掉中间蓝色指针省下的部分);指针图片(中间蓝色的指针部分,不包括指针上的白色圆点);原点图片(中间白色的圆点)原理:在paintEvent()中绘制这三张图片。当需要旋转指针的角度时,改变指针图片的角度,再调用update()函数重新绘制显示就可以了。具体实现:1. 定义旋转角度成员变量:int m_nValue;//指针旋转角度。2. 重载paintEvent()函数。3. 加载三张图片。QPixmap img = QPixmap(":/image/banhuan.png");QPixmap needle = QPixmap(":/image/zhizhen.png");QPixmap overlay = QPixmap(":/image/zhizhenyuan.png");4. 在paintEvent()函数中进行绘制。void CDialBox::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.save();//保存 painter.setRenderHint(QPainter::SmoothPixmapTransform, true); //平滑像素图,防止图形走样 painter.translate(this->width() / 2, this->height() / 2); // 原点定位在中间位置 // 背景图 painter.drawPixmap(-img->width() / 2, -img->height() / 2, img); // 指针图 painter.rotate(m_nValue);//设置旋转角度 painter.drawPixmap(-needle.width() / 2, -needle.height() + needle.width() / 2, needle); //原点图 painter.drawPixmap(-overlay.width() / 2, -overlay.height() / 2, overlay); painter.restore();//恢复 }其中:painter.save();painter.restore();为保存QPainter当前的状态和恢复QPainter当前的状态。这里使用这两个函数主要是服务于painter.translate()函数的。因为painter.translate(x, y)函数用来设置当前QPainter的相对坐标。正常QPainter的坐标原点(0,0)在屏幕的左上角,调用painter.translate(x, y)函数,会将原点设置为指定的(x,y)的位置,也就是说屏幕的(x,y)为QPainter画布的(0,0)位置。为了防止画布上的图片有缩放或拉伸导致的图像走形,可以调用painter.setRenderHint(QPainter::SmoothPixmapTransform, true);来进行平滑设置。在绘制图形时顺序很重要,先调用painter.drawPixmap()函数进行绘制的图形在最下面,后面调用的会覆盖在之前的图形之上,所以根据效果图,应该先绘制背景图片,再绘制指针图片,最后绘制原点图片。因为此时原点(0,0)的位置在效果图的最中间位置,所以绘制背景图时的(x,y)应该为(-img->width() / 2, -img->height() / 2);指针图的X值为负的宽度的一半,Y值为负的高度的值加上宽度的一半。 调用painter.rotate()函数是用来将画布以坐标原点为中心进行顺时针旋转指定的角度。所以定义一个成员变量来给这个角度进行传值。原点图的(x,y)应该为(-overlay.width() / 2, -overlay.height() / 2);5.改变指针角度定义一个改变指针角度值的函数void valueChanged(int value);void CDialBox::valueChanged(int value) { m_nValue = value; update(); }设置角度的值,调用update()函数进行刷新。调用update();函数会执行paintEvent();
环境:VS2017+Qt5.14.2程序中有时需要读取文件内容的功能,读取文件内容一般涉及到提取应用程序路径、提取目录或文件信息、打开文件、提取文件信息等功能。读取文件内容和删除文件的相关的操作类有:QFile:除了打开文件操作外,QFile还有复制文件、删除文件等功能。QDir:用于提取目录或文件信息,获取一个目录下的文件或目录列表,创建或删除目录和文件,文件重命名等操作。1.首先获取程序当前的运行路径,然后拼接文件路径。QString qstrPath = QDir::currentPath();qstrPath += "\Files\";2.判断路径是否存在。QDir dir(qstrPath);bool ret = dir.exists();QDir是进行目录操作的类,在创建QDir对象时传递一个目录字符串作为当前目录,然后QDir函数就可以针对当前目录或目录下的文件进行操作。在操作之前可以调用exists()函数判断一下传递的目录是否存在,如果存在再进行操作。3.过滤文件类型。QStringList filters; filters << QString(".txt") << QString(".doc");//设置需要筛选出来的文件的后缀名,可以设置多种文件类型。dir.setFilter(QDir::Files | QDir::NoSymLinks);//设置类型过滤器只为文件格式,QDir::Files为只列出文件,QDir::NoSymLinks不列出符号连接(不支持符号连接的操作系统会忽略它,什么意思我也不太清楚)dir.setNameFilters(filters);//这个函数会将我们设置的文件格式设置进去,就可以得到我们想要的格式的文件了。到这里,所有符合我们设置的文件都已经筛选出来了。调用dir.count()可以获取到筛选出的文件的个数。4.读取文件内容并删除文件。for(int i=0; i<dir.count(); i++) { QByteArray data; QFile file; QString qstrFileName = qstrName + dir[i]; file.setFileName(qstrFileName); if(file.open(QIODevice::ReadOnly)) { data = file.readAll(); file.close(); file.remove(); } }setFileName()为设置文件路径名称,设置文件路径名之后,调用open()函数打开文件,参数为可以对文件进行的操作的类型,分为只读、只写、可读可写。打开文件后调用readAll()函数将文件中的内容全部读到QByteArray里面,然后调用close()函数关闭文件。调用remove()函数可以将这个文件删除。
环境:VS2017+Qt5.14.21. 添加SerialPort模块下面是在VS中添加NetWork模块的流程,两个流程,任选一个就可以了。右键项目->选择属性->选择Qt Project Settings->Qt Modules->勾选SerialPort点击导航栏Qt VS Tools->选择Qt Project Settings->Qt Modules->勾选SerialPort2. 声明QSerialPort成员变量。QSerialPort *m_serialPort;m_serialPort = new QSerialPort();QSerialPort类提供访问串口的功能,还可以通过QSerialPortInfo类获取可用串口的信息。QSerialPortInfo类允许枚举系统中所有串口的信息,包括串口名称、系统位置、描述和制造商。 QList<QSerialPortInfo) QSerialPortInfo::availablePorts();返回系统上可用串口的列表。3. 设置串口信息。设置串口名: m_serialPort->setPortName("COM3");设置波特率: m_serialPort->setBaudRate(QSerialPort::Baud9600);//是一个衡量符号传输速率的参数。设置数据位: m_serialPort->setDataBits(QSerialPort::Data8);//是衡量通信中实际数据位的参数。设置奇偶校验: m_serialPort->setParity(QSerialPort::OddParity);//在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。设置停止位: m_serialPort->setStopBits(QSerialPort::OneStop);//用于表示单个包的最后一位。设置流控制: m_serialPort->setFlowControl(QSerialPort::NoFlowControl);设置读取数据的缓存大小: m_serialPort->setReadBufferSize(40960);串口名称是必须要设置的,剩下的波特率、数据位、奇偶校验位、停止位、流控制不是必须要设置的,如果有特殊需求需要设置的时候,可以再进行设置,以上参数仅为参考,可以根据自己的情况查看参数值都有哪些。读取数据的缓存大小是必须要设置的,它的大小决定了串口收到数据后可以缓存的数据的大小,如果长时间不将数据从缓冲区取出来,会出现溢出丢数据的情况。所以这个缓存的大小可以根据实际的通信速度和数据量进行设置。4.打开串口。m_serialPort->open(QIODevice::ReadWrite);参数可以设置串口为只读、只写、读写三种方式。5.打开监听消息线程。std::thread pListenThread = std::thread(&Myself::ListenThreadFuc, this); pListenThread.detach();采用开线程的方式定时去读取串口中缓存的数据,这样做是为了不造成堵塞。6.接收数据。void Myself::ListenThreadFuc() { if (m_serialPort == nullptr) { return; } while (m_serialPort->isOpen()) { QByteArray readData = m_serialPort->read(DATA_SIZE); if (!readData.isEmpty()) { emit ReadData(readData); } Sleep(100); } }读数据可以使用read()函数,read()函数指定每次读出的数据的大小。也可以使用readAll()函数,readAll()函数会将串口中缓存的数据全部读出。7.发送数据。QByteArray byteSendData;m_serialPort->write(byteSendData);8.关闭串口。m_serialPort->clear();//清除输入输出缓冲区里面的数据m_serialPort->close();//关闭串口设备m_serialPort->deleteLater();//不立即销毁,父类销毁时再销毁
当程序中需要播放音频文件(.mp3)时,可以使用QMediaplayer实现这个功能。环境:VS2017+Qt5.14.21. 添加Multimedia模块。下面是在VS中添加Multimedia模块的流程,两个流程,任选一个就可以了。右键项目->选择属性->选择Qt Project Settings->Qt Modules->勾选Multimedia点击导航栏Qt VS Tools->选择Qt Project Settings->Qt Modules->勾选MultimediaMultimedia模块为Qt的多媒体功能模块,它提供了很多类,可以实现播放压缩音频(MP3、AAC)、播放音效文件(WAV)、播放低延迟的音频、访问原始音频输入数据、录制编码的音频数据、发现音频设备、视频播放、视频处理、摄像头取景框、取景框预览处理、摄像头拍照、摄像头录像、收听数字广播等功能。2. 声明并创建QMediaplayer成员变量。QMediaPlayer *m_pMediaPlayer;m_pMediaPlayer = new QMediaPlayer(this);3.设置需要播放的音频文件(.mp3)的路径。QString qstrpath = ".\FILES\Sound\sound.mp3";m_pMediaPlayer->setMedia(QUrl::fromLocalFile(qstrpath));QMediaPlayer类可以通过调用setMedia()函数设置播放单个文件。也可以通过调用setPlayList()函数设置一个QMediaPlayList类的实例来表示的播放列表,对列表中的文件进行播放,并且自动播放下一个文件或循环播放等。QMediaPlayer类播放的文件可以是本地文件,也可以是网络上的媒体文件。4.设置音量。m_pMediaPlayer->setVolume(50);设置播放的音量,参数范围在0~100之间。当参数为0时可以实现静音的效果。设置是否静音还可以使用setMuted(bool muted)来实现。bool isMuted()函数可以返回静音状态,返回值为ture表示当前为静音状态。int volume()函数可以得到播放的音量。注:如果定义了多个QMediaPlayer变量,设置了其中一个播放器变量的音量,那么其他播放器变量的音量也会改变。5.播放音频。m_pMediaPlayer->play();暂停播放使用pause()函数。停止播放使用stop()函数。6.删除QMediaplayer成员变量。if(m_pMediaPlayer != nullptr) { delete m_pMediaPlayer; m_pMediaPlayer = nullptr; }注,在使用完创建的QMediaPlayer对象后,要记得销毁,否则会造成内存泄漏。特别:当定义的QMediaPlayer对象正在播放一个音频文件时,再次调用setMedia()和play(),会停止正在播放的音频文件,播放新设置的音频文件。如果想同时播放两个音频文件,需要创建两个QMediaPlayer对象,同时进行播放。重复new()和delete()会造成崩溃。
环境:VS2017+Qt5.14.21. 添加NetWork模块。下面是在VS中添加NetWork模块的流程,两个流程,任选一个就可以了。右键项目->选择属性->选择Qt Project Settings->Qt Modules->勾选NetWork点击导航栏Qt VS Tools->选择Qt Project Settings->Qt Modules->勾选NetWorkQt网络模块提供一些实现OSI 7层网络模型中高层的网络协议,如HTTP、FTP、SNMP等,这些类主要是QNetworkRequest、QNetworkReply、QNetworkAccessManager。QNetworkAccessManager类用于协调网络操作。在QNetworkRequest发起一个网络请求后,QNetworkAccessManager类负责发送网络请求,创建网络相应。 QNetworkReply类表示网络请求的响应。由QNetworkAccessManager在发送一个网络请求后创建一个网络响应。QNetworkReply提供的信号finished()、readyRead()和downloadProgress()可以检测网络响应的执行情况,执行相应操作。2. 声明QNetworkAccessManager成员变量。QNetworkAccessManager m_networkManager;QNetworkAccessManager 是Qt中的网络访问管理器,主要用来实现Qt中的网络编程。这个类允许应用程序发送网络请求和接收网络应答。QNetworkAccessManager类通过一个URL地址发起网络协议请求,也保存网络请求的信息,目前支持HTTP、FTP和局部文件URLs的下载或上传。3. 连接信号槽函数。connect(&m_networkManager, &QNetworkAccessManager::finished, this, &Myself::OnReplyFinished);当请求图片资源完成时,会响应QNetworkAccessManager::finished()信号,它的请求是异步的。每当网络应答结束时都会发射这个信号。 函数原型为:void finished(QNetworkReply *reply);4. 请求图片资源。QNetworkRequest request;request.setUrl(QUrl(“http://”));m_networkManager.get(request);用get()函数来发送一个网络请求。除了get()函数,管理器还提供了发送HTTP POST请求的post()函数。5. 槽函数的实现。void MySelf::OnReplyFinished(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray bytes = reply->readAll(); QPixmap pixmap; pixmap.loadFromData(bytes); //按比例将图片缩放成固定大小 pixmap = pixmap.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); } reply->deleteLater(); }注: reply->deleteLater();一定要调用,否则会造成内存泄露特别:如果访问Http没有问题,而访问Https时图片获取失败,是因为Https访问需要用到SSL认证,而QT默认是不支持SSL认证的,所以还需要安装OpenSSL库:1. 打开slproweb.com/products/Wi…网页;2. 下载安装包;我下载的是: Win64 OpenSSL v1.1.1m Light 安装包,随着时间的推进,这个版本会不断更新的;3. 安装(exe文件)到本地,并且在安装过程中选择将库安装到OpenSSL的安装目录(/bin)下面。4. 将 libeay32.dll 和 ssleay32.dll 文件拷贝到Qt中的存放有QtNetwork4.dll和QtNetworkd4.dll的文件目录下。我的路径是 D:\Qt\5.14.2\5.14.2\msvc2017_64\bin。
在Qt中使用定时器一般有三种方式:一、直接使用QObject类提供的定时器。1.在需要开启定时器的地方直接调用startTimer();该函数的声明为:int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);该函数开启一个定时器,返回值是定时器的编号。参数一为时间间隔,单位毫秒;参数二为定时器的精确度:Qt::PreciseTimer(精确的定时器,尽量保持毫秒精度,试图保持精确度在1毫秒);Qt::CoarseTimer(粗略的定时器,尽量保持精度在所需的时间间隔5%范围内);Qt::VeryCoarseTimer(很粗略的定时器,只保留完整的第二精度,大约为500毫秒);2.重载void QObject::timerEvent ( QTimerEvent * event ); 当定时器溢出时,会自动响应timerEvent()函数。在timerEvent()函数中,通过event->timerId()来确定是哪个定时器触发的;3.在需要关闭定时器的地方调用killTimer();该函数的声明为: void killTimer(int Id);该函数关闭一个定时器,参数为定时器的编号。二、使用QTimer类。1.用new的方式创建一个QTimer对象。QTimer *timer = new QTimer(this);2.将定时器的溢出信号连接到自定义的槽函数。connect(timer, &QTimer::timeout, this, &Myself::update);3.启动定时器。timer->start(1000);函数原型为:void start(int msec);参数为定时器时间间隔,单位毫秒。也可以调用timer->setInterval(1000);设置定时器时间间隔,然后调用timer->start();开启定时器。4.停止定时器。timer->stop();三、仅调用一次溢出的定时器。QTimer::singleShot(1000, this, SLOT(OnSendBreath()));函数原型有两个:1.static void singleShot(int msec, const QObject *receiver, const char *member);参数一为时间间隔,单位毫秒;参数二为接收溢出信号的对象;参数三为溢出信号的槽函数;2.static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member);参数一为时间间隔,单位毫秒;参数二为定时器的精确度(同上文);参数三为接收溢出信号的对象;参数四为溢出信号的槽函数。另:都可以用到的一些函数:1. 判断定时器是否正在运行:bool QTimer::isActive () const2. 改变定时器的时间间隔:void QTimer::changeInterval ( int msec )如果这个定时器正在运行,他将被停止并且重新开始,否则将会被开始。
环境: VS2017+Qt5.14实现功能: 一个QWidget窗口,初始状态是隐藏的,在这个QWidget中有一个QLabel, 在QWidget显示的同时,打开定时器,定时器时间间隔为42ms,在定时器响应函数中执行QLabel上图片的定时切换,从而达到动画的效果。具体实现: QWidget执行show()之后,立马打开定时器,在定时器的timeout()中执行ui.label->setPixmap(m_Pixmap[i]);遇到的问题: show()函数执行完毕,定时器start()执行完毕,但是定时器的响应函数迟迟不响应。有时可能等一两秒就可以反应过来,有时可能需要八九秒才可以反应过来,反应过来后,定时器正常运行,动画正常显示。或者在定时器不响应的时候,用鼠标在窗口上移动一下,定时器的响应函数就可以正常响应了。但是响应之前就像界面卡住了一样。于是开始查找原因及解决办法,把所有相关代码注释掉后一点点放开,发现造成这种现象的原因就是因为调用show()函数引起的。这就有点鸡肋了,如果是因为show()函数引起的,说明是主线程在走UI的一些东西卡住了,这怎么解决?!在网上查了很多种解释和方法,show()和hide()函数确实会引起界面刷新,从而导致主线程卡住,如果是静态页面,一般不会有什么问题,但是如果是动态页面,就会出现程序卡住的现象。解决办法: 一种方法是使用stackWidget,调用stackWidget的setCurrentIndex()实现页面的切换,避免使用show()、hide()函数。我试过这种方法,但是因为我们的窗口样式和父子继承关系很复杂,所以stackWidget加入进来后,显示效果并不能达到我们的需求,所以放弃这种方法。另一种方法是一个比较有局限性的方法,就是调用Qt的事件轮循机制。m_bExit = false; while (!m_bExit) { QCoreApplication::processEvents(QEventLoop::AllEvents, 100); }在需要退出事件轮循的地方将m_bExit = true;调用事件轮循后,界面就不会出现卡死的现象了,因为在处理主进程的同时可以去处理其他的消息、事件。这样就实现了同时处理主进程和其他线程的事件,达到在线程中刷新界面的效果。注:因为我的这部分代码属于在show()的同时操作界面,所以即使开线程,也需要在线程中发信号出来,执行相应操作,所以如果用线程+信号槽机制还是会卡死。目前上面这种方法是我试过的效果最好的方法,但是他会有一个问题,就是如果在这个while()循环执行期间退出程序,程序是退不掉的,需要手动调用m_bExit = true,或者销毁资源的函数,先退出while循环,然后再执行程序的close()才可以。
最近遇到一个问题,希望可以有大佬可以探讨一下。环境:VS2017+Qt5.14.2; 工具->扩展和更新->Qt Visual Studio Tools->版本:2.5.1项目中添加了一个纯C++类的.h和.cpp,为了可以在这个类中使用Qt的信号槽机制,将.h中的纯C++类继承QWidget,并且在类中添加Q_OBJECT。曾经使用的Qt Visual Studio Tools版本是2.7.1,编译运行是没有问题的,后改成版本2.5.1后出现以下错误:编译时报错: 1>c1xx : fatal error C1083: 无法打开源文件:“x64\Debug\Res\src\Vlc\include\moc_AVPlayer.cpp”: No such file or directory。moc_AVPlayer.cpp文件是在程序编译过程中动态生成的,经过查找发现moc_AVPlayer.cpp文件是成功生成了的,但是生成的路径是x64\Res\src\Vlc\include,生成路径和报错路径不一致,正常情况下,moc_.cpp文件应该生成在x64\Debug\moc或x64\Release\moc目录下,不知道为什么这个文件的生成路径和其他文件的生成路径不一致,并且生成成功后,编译找的路径不对。解决办法: 将生成的moc_.cpp放到指定目录下,再编译就成功了。但是这个方法没办法从根上解决这个问题,一旦这个文件发生变化的时候,还需要再手动将moc_.cpp放到指定目录下。之前遇到过类似情况,解决办法是:在VS中选中该文件的.h,右键打开属性,将常规中的项类型改为其他编译正常的文件的项类型,就可以了。但是这个文件的属性中的项类型,以及项类型的详细信息的设置都和其他文件一模一样,尝试过1.将文件改名重新加入;2.重新设置文件属性;3重新添加文件后编译都不行。感觉是工具集版本不一致造成的,但是具体原因和解决办法还是没有找到。总结:如果没有成功生成moc_xxx.cpp文件,应该在VS中选中该文件的.h,右键打开属性,将常规中的项类型改为其他编译正常的文件的项类型。 如果成功生成了moc_xxx.cpp文件,只是编译路径与生成路径不符,应该手动将生成的moc_.cpp放到指定目录下。如果有更好的解决办法,希望可以在评论区聊一下!
读取配置文件:1.定义文件路径:CString strFilePath=_T(".\FILES\config.ini");2.判断文件是否存在:if (!PathFileExists(strFilePath)){return;}3.读取数据:int nkeyValue = GetPrivateProfileInt(_T("AppName"),_T("KeyName"),TRUE,strFilePath);//(AppName, KeyName, 默认值, 文件名)Cstring strKeyValue = ""; GetPrivateProfileString(_T("AppName"),_T("KeyName"),_T(""),strKeyValue .GetBuffer(MAX_PATH),MAX_PATH,strFilePath); //(AppName, KeyName, 默认值,返回值,大小,文件名)strKeyValue .ReleaseBuffer(); //千万不能少这句话写入配置文件:1.定义文件路径:CString strFilePath=_T(".\FILES\config.ini");2.判断文件是否存在:if (!PathFileExists(strFilePath)){return;}3.写入数据:Cstring strKeyValue = _T("keyname");WritePrivateProfileString(_T("AppName"),_T("KeyName"),strKeyValue ,strFilePath); //(AppName, KeyName, 写入值, 文件名)
1. 定义全局变量: CRect rect[2] = {(0,0,0,0),(0,0,0,0)}; // 分别存放两个屏幕的坐标2. 定义全局函数:BOOL CALLBACK Monitorenumproc( HMONITOR hMonitor,HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { static BOOL first = FALSE; //重复检测标志 MONITORINFO monitorinfo; //显示器信息结构体 monitorinfo.cbSize = sizeof(MONITORINFO); GetMonitorInfo(hMonitor, &monitorinfo); //获取显示器信息 if(monitorinfo.dwFlags == MONITORINFOF_PRIMARY) //如果是主显示器 { if(!first) //如果是第一次检测到主显示器 { first = TRUE; rect[0] = monitorinfo.rcMonitor; //将主显示器的坐标信息存到第一个位置 return TRUE; }else { first = FALSE; return FALSE; } }else//如果不是主显示器,将显示器的坐标信息存到第二个位置 { rect[1] = monitorinfo.rcMonitor; } return TRUE; }3. 在主函数调用: EnumDisplayMonitors(NULL, NULL, Monitorenumproc, 0); // 枚举屏幕的系统函数
1、定义内存DC: CDC memDC;2、定义位图资源: CBitmap bitmap;3、创建内存DC: memDC.CreateCompatibleDC(&dc);4、创建位图资源: bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());5、选择位图资源: CBitmap *pOldBitmap = memDC.SelectObject(&bitmap);6、双缓存输出到屏幕: dc.BitBlt(0, 0, rect.Width(),rect.Height(),&memDC, 0, 0, SRCCOPY);7、恢复位图资源: memDC.SelectObject(pOldBitmap);8、释放资源: bitmap.DeleteObject();memDC.DeleteDC();ReleaseDC(&memDC);根据自己的需要可以定义为成员变量,也可以在OnPaint()中使用临时变量。
GDI: (Graphics Device Interfase)图形设备接口,是一个应用程序与输出设备之间的中介。运行环境: Gdiplus.dll 包含在Windows系统中。【在system32中包含Gdiplus.dll文件】。1.包含头文件: #include <Gdiplus.h>2.链接库文件: 属性->配置->连接器->输入->附加依赖项->Gdiplus.lib;3.定义成员变量: ULONG_PTR m_gdiplusToken;4.在CMYAPP类的函数InitInstance()中加入: GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);5.在CMYAPP类的函数ExitInstance()中加入: GdiplusShutdown(m_gdiplusToken);6.一个Text属性结构体:typedef struct{ RectF rectF;//文字区域 Color color;//文字颜色 CString text;//文本 int fontSize;//文字大小 Gdiplus::StringAlignment styleX; //水平对齐方式 Gdiplus::StringAlignment styleY; //垂直对齐方式 Gdiplus::StringFormatFlags styleWrap; //是否换行 int fontArial; //是否粗体 CString fontStyle; //字体名称}m_Text;7.一个Image属性结构体:typedef struct{ RectF rectF;//图片区域 CString szPath; //图片路径}m_Image;8.绘制文字:Void SetGDIFont(m_Text text, HDC hdc){ Graphics graphics(hdc); SolidBrush brush(text.color); //字体颜色 FontFamily fontFamily(text.fontStyle); Gdiplus::Font font(&fontFamily, text.fontSize, text.fontArial, UnitPixel); RectF rectF(text.rectF); graphics.SetTextRenderingHint(TextRenderingHintAntiAlias); // 平滑处理 StringFormat stringformat = new StringFormat; stringformat.SetAlignment(text.styleX); stringformat.SetLineAlignment(text.styleY); graphics.DrawString(text.text,-1,&font,rectF,&stringformat,&brush); //绘制 graphics.ReleaseHDC(hdc);}9.绘制图片:void SetGDIImage(m_Image image, HDC hdc){ Graphics graphics(hdc); Image image(szImagePath,FALSE); graphics.DrawImage(&image, image.rectF.left, image.rectF.top, image.rectF.right-rectF.left, image.rectF.bottom-rectF.top); //绘制 graphics.ReleaseHDC(hdc);}
2022年07月