Qt可以比较方便地实现自定义控件在Qt Creator中使用。网上也有很多大神的控件可以使用,但是如果想要自己简单定制也可以按照这个流程。
本文的要点:
【1】如何实现一个自定义控件?
本文使用的方法有两个步骤:
【2】自定义控件如何在多个环境下使用?
因此,文章也分两方面去阐述,第一部分编写,和第二部分使用。
首先,新建一个QWidget项目。
界面上拖拽一个文字显示框(QPlainTextEdit)和一个垂直滑块(QSlider)如图:
然后,新建一个类MySlider,继承于QSlider,Q_OBJECT宏记得要加上。
#ifndef MYSLIDER_H
#define MYSLIDER_H
#include
#include
class MySlider : public QSlider
{
Q_OBJECT
public:
MySlider(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *ev) override;
void mousePressEvent(QMouseEvent *ev) override;
void mouseReleaseEvent(QMouseEvent *ev) override;
void mouseMoveEvent(QMouseEvent *ev) override;
private:
std::atomic_bool mValiedMouseFlag{false};
std::atomic_int mSilderUp{0};
};
#endif // MYSLIDER_H
重写的几个方法可以点击到头文件里查看哪些用得到需要重写:
这里重写的是:
void paintEvent(QPaintEvent *ev) override; //绘制控件
void mousePressEvent(QMouseEvent *ev) override; //鼠标按下
void mouseReleaseEvent(QMouseEvent *ev) override; //鼠标松开
void mouseMoveEvent(QMouseEvent *ev) override; //鼠标移动
还有需要注意的是在protected区域的对象虽然重写在public也可以运行,也要保持不改,因为编译成控件以后有可能会不识别。
至于下面的两个变量:
mValiedMouseFlag用于保存鼠标按下状态(对于一些关键的变量习惯用atom 可以改成普通的bool类型)
mSilderUp用于保存按下鼠标时,鼠标指针离滑块顶部的距离(对于一些关键的变量习惯用atom 可以改成普通的int类型)
然后开始在cpp内实现几个方法,首先是绘图:
//滑块高度固定40
const int heightSlider = 40;
void MySlider::paintEvent(QPaintEvent *ev)
{
Q_UNUSED(ev);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing,true);
QRect rect = QRect(4,4,this->width()-8,this->height()-8);
QBrush brush; //画刷
brush.setColor(QColor(52,123,226,150));
brush.setStyle(Qt::SolidPattern);
painter.setBrush(brush);
//传入绘制区域或者直接放入QRect作为参数
painter.drawRoundedRect(rect, this->width()/4, this->width()/4);
//最大值
int maxValue = this->maximum();
//当前值
int value = this->value();
//滑块最低的高度值(最高为0)
int heightAll = this->height()-heightSlider;
//滑块y的值
int y = (maxValue* heightAll - value*heightAll) / maxValue;
//中间的白线
int centerX = this->width()/2-5;
int centerY = 10;
int centerW = 10;
int centerH = this->height() - 20;
QRect centerRect = QRect(centerX, centerY, centerW, centerH);
brush.setColor(QColor(255, 255, 255, 255));
painter.setBrush(brush);
painter.drawRoundedRect(centerRect, 5, 5);
//绘制滑块
QRect sliderRect = QRect(0,y,this->width(),heightSlider);
brush.setColor(QColor(255,255,255,230));
painter.setBrush(brush);
//传入绘制区域或者直接放入QRect作为参数
painter.drawRoundedRect(sliderRect, 10, 10);
}
这里用到的也比较简单的几个绘图(如果不了解QPainter可以搜相关教程,比较容易理解的绘图类):
1、绘制背景(圆角矩形)
2、绘制中间白线(圆角矩形)
3、计算滑块的位置
4、绘制滑块(圆角矩形)
绘制完成后大概就是这样了(简单调整了下大小,MainWindow的MenuBar删除了,还改成了栅格布局):
接下来实现拖动滑块的处理,如果不去管它,那这个只能是披着一个新外衣的旧控件,里面可操作的部分还是旧控件的部分。
void MySlider::mousePressEvent(QMouseEvent *ev)
{
int maxValue = this->maximum();
int value = this->value();
int heightAll = this->height()-heightSlider;
int y = (maxValue* heightAll - value*heightAll) / maxValue;
//判断是否在滑块区域内
if ((ev->pos().y()> y) && (ev->pos().y()< y+heightSlider))
{
mValiedMouseFlag.store(true);
//记录鼠标与滑块上下方距离
mSilderUp.store(ev->pos().y() - y);
}
else {
//如果在滑块外,则按照参数前进或者后退。
if(ev->pos().y()< y){
this->setValue( this->value() + this->pageStep());
}else if (ev->pos().y()> y+heightSlider) {
this->setValue( this->value() - this->pageStep());
}
}
}
void MySlider::mouseReleaseEvent(QMouseEvent *ev)
{
Q_UNUSED(ev);
mValiedMouseFlag.store(false);
}
void MySlider::mouseMoveEvent(QMouseEvent *ev)
{
if(mValiedMouseFlag.load()){
int tempValue{0};
int y = ev->pos().y();
double k = 1;
//总高度
int heightAll = this->height()-heightSlider;
//滑块左上角顶点应该要在的高度(范围在0~总高度之间)
int sY= y-mSilderUp.load();
sY = sY<0 ? 0 :sY;
sY = sY>heightAll ? heightAll :sY;
//下面两步计算滑块应该要在的高度对应的值
k= (double)(heightAll - sY)/(double)heightAll;
tempValue = k* this->maximum();
this->setValue(tempValue);
}
}
其实代码也不多:
1、mousePressEvent判断鼠标按下是否在滑块区域内:
如果是的话,就记录下与滑块顶部的距离,并且设置mValiedMouseFlag为true
否则就判断在滑块上面还是下面,步进改变控件value的值。
2、mouseReleaseEvent ,设置mValiedMouseFlag为false
3、mouseMoveEvent,鼠标移动,如果判断mValiedMouseFlag为true则说明在拖动滑块,否则什么也不做。当发现在拖动滑块时,按照位置来改变value的值(这里涉及到一点点不难的计算)。
可见,这3步,没有去改变界面,只是改变了value的值,让控件自行改变,这个是QSlider自带的功能,会自动调用paintEvent重绘图像,如果写其他控件或者其他功能不能重绘的,需要手动调用update()也可以重绘图形。
然后为了检验是否成功,可以在主界面中重写valueChanged(也可以写qDebug打印)
void MainWindow::on_verticalSlider_valueChanged(int value)
{
ui->plainTextEdit->setPlainText(QString("value:%1").arg(value));
}
运行:
到这一步就完成了在普通项目中使用了。
首先查看Qt Creator的版本
如果没有这个版本的编译环境就要去下载配置。
新建项目:
选择以后需要用到的环境(MinGW等其他的也可以以后再选)
组的部分可以写 Test Widgets [user] 之后好在界面上区分
然后一路下一步完成。
项目建立后,发现刚才建立的类名称,将之前项目的类的h和cpp文件内容替换:
除此之外,头文件还要再修改一下:
class MySlider : public QSlider
改成:
#include
class QDESIGNER_WIDGET_EXPORT MySlider : public QSlider
另外还要修改一个地方:
头文件改成 #include
否则之后有可能会有警告。
选择Qt creator的编译环境的release版本编译:
出现了警告:
这个应该大家都遇到过,源码中有中文或者中文注释,而编码又是utf-8不带bom会出现这个。方法有很多,最简单的方法是在pro文件最后加上:
contains( CONFIG, "msvc" ) {
QMAKE_CXXFLAGS += /utf-8
QMAKE_CFLAGS += /utf-8
}
看到已经生成了dll
一般是项目目录同级的编译目录下
将这个dll拷贝到QtCreator的plugins目录下:我的是 D:\Qt\Tools\QtCreator\bin\plugins\designer
新建一个项目发现已经有这个控件已经在右下角了(但是还不能马上直接使用,还需要链接库和头文件。使用见下一小节)
(如果之前头文件没有加Q_OBJECT宏的话,会识别不出MySlider,只能识别为QSlider)
至此Qt Creator控件编译的过程就结束了。
目前编译的版本是Qt5.15.2 MSVC2019_64 Release,如果之后还想要在其他环境编译使用,则把其他环境都编译一遍:(如果不用其他版本,本小结后面编译可以不看了。)
Qt5.15.2 MSVC2019_64 Debug:点击编译即可。
生成了带d的库文件和lib文件
之后编译成功。
第一部分编译的头文件和库文件可以保存在不同的目录,可以方便程序清晰地调用。
下面是目录结构 D:\code\common\ui
ui
├─include
│ └─MySlider
│ MySlider.h
│
└─lib
├─mingw81_64
│ ├─Debug
│ │ └─MySilder
│ │ libmysliderplugin.a
│ │ mysliderplugin.dll
│ │
│ └─Release
│ └─MySilder
│ libmysliderplugin.a
│ mysliderplugin.dll
│
└─MSVC2019_64
├─Debug
│ └─MySilder
│ mysliderplugind.dll
│ mysliderplugind.lib
│
└─Release
└─MySlider
mysliderplugin.dll
mysliderplugin.lib
每个dll和lib都从对应的编译目录中提取,例如:mingw81_64的Debug文件就从编译的目录中取出.a和.dll 文件:
新建一个项目,使用例子中的两个界面:
现在直接编译会报错:
在pro增加头文件和链接库(注意MSVC是带d的):
INCLUDEPATH += D:/code/common/ui/include/MySlider
CONFIG +=debug_and_release #如果项目属性里配置了,这里可以省略
CONFIG(debug,debug|release){
message("debug")
msvc{
message("msvc")
LIBS += -LD:/code/common/ui/lib/MSVC2019_64/Debug/MySilder/
LIBS += -lmysliderplugind
}else{
message("mingw")
LIBS += -LD:/code/common/ui/lib/mingw81_64/Debug/MySilder/
LIBS += -lmysliderplugin
}
}ELSE {
message("release")
msvc{
message("msvc")
LIBS += -LD:/code/common/ui/lib/MSVC2019_64/Release/MySilder/
LIBS += -lmysliderplugin
}else{
message("mingw")
LIBS += -LD:/code/common/ui/lib/mingw81_64/Release/MySilder/
LIBS += -lmysliderplugin
}
}
编译运行,就可以愉快地使用了。
再加上之前例子的代码:
void MainWindow::on_mySlider_valueChanged(int value)
{
ui->plainTextEdit->setPlainText(QString("Value:%1").arg(value));
}
就完成了。
打包之类的不再赘述。