前言
Qt 重新绘制ComboBox,原因无他,没想到自己画的更丑。自定义控件看这篇,这里就直接画了
一、基类?
继承QComboBox
,继承QWidget
更好,相当于自己重新写个控件,这里主要是懒,想用现成的东西……
二、使用步骤
1.代码文件
代码如下(没啥好看的,瞎写的废话),完整项目压缩包放最后:
.h文件
#ifndef SELFCOMBOBOX_H
#define SELFCOMBOBOX_H
#include <QComboBox>
class SelfComboBox : public QComboBox
{
Q_OBJECT
public:
SelfComboBox(QWidget *parent = 0);
~SelfComboBox();
void paintEvent(QPaintEvent *e) override;
//覆盖
void addItem(const QString &text, const QVariant &userData = QVariant());
void insertItem(int index, const QString &text, const QVariant &userData = QVariant());
//属性
Q_PROPERTY(qreal itemHeight READ getItemHeight WRITE setItemHeight);
Q_PROPERTY(QFont font READ getFont WRITE setFont);
Q_PROPERTY(double viewSpace READ getViewSpace WRITE setViewSpace);
protected:
void focusOutEvent(QFocusEvent *e) override;
public slots:
void wheelEvent(QWheelEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *e) override;
void onTimerCallBack();
void onViewTimerCallBack();
double getItemHeight(){return itemHeight;}
void setItemHeight(double iHeight);
QFont getFont(){return font;}
void setFont(QFont f);
double getViewSpace(){return viewSpace;}
void setViewSpace(double vs);
private:
double radius = 1;
double maxRadius = 255;
double minRadius = 1;
bool isExpand = false;
double yOffect = 0;
bool isViewOpen = false;
int curOpenNum = 0;
int maxNum = 4;
int curDrawNum = 0;
int curTopIndex = 0;
int curClickNum = -1;
int roundRadius = 8;
qreal itemHeight = 0.0;
int space = 2;
QFont font = QFont("SimHei", 18);
double viewSpace = 15.0;
// QString comboStr = "Select";
QRect *activePoints = nullptr;
//数据存储
QStringList allData;
QVariantList allVData;
QTimer *timer = nullptr;
QTimer *viewTimer = nullptr;
void drawRoundedView(QPainter *);
void drawRoundedViewTitle(QPainter *);
void onComboBtnClick();
void onAnimChange(bool isExpand);
};
#endif // SELFCOMBOBOX_H
.cpp文件 挑重点发……
void SelfComboBox::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.setRenderHint(QPainter:: Antialiasing, true);
this->drawRoundedViewTitle(&painter);
//最主要的绘制地方就是这个啦
this->drawRoundedView(&painter);
}
//重写
void SelfComboBox::addItem(const QString &text, const QVariant &userData)
{
this->allData.push_back(text);
this->allVData.push_back(userData);
if(this->allData.length() == 1)
{
this->setCurrentIndex(0);
this->setCurrentText(text);
}
//懒得维护count之类的……
QComboBox::addItem(text, userData);
update();
}
void SelfComboBox::focusOutEvent(QFocusEvent *e)
{
if(this->viewTimer && this->viewTimer->isActive())
{
this->viewTimer->stop();
}
this->isViewOpen = false;
this->viewTimer->start(10);
}
//下方区域滚动
void SelfComboBox::wheelEvent(QWheelEvent *event)
{
this->yOffect += (event->delta() * 0.25);
this->yOffect = this->yOffect <= 0.0 ? 0.0 : this->yOffect;
update();
}
void SelfComboBox::mousePressEvent(QMouseEvent *event)
{
if(this->viewTimer && this->viewTimer->isActive())
{
this->viewTimer->stop();
}
QRect detectArea(0, 0, width(), itemHeight);
if(detectArea.contains(event->pos()))
{
this->isViewOpen = !this->isViewOpen;
this->viewTimer->start(50);
}
//下方区域检测
this->curClickNum = -1;
if(event->button() == Qt::LeftButton)
{
for (int i = 0; i < this->curDrawNum; i++)
{
if(this->activePoints[i].contains(event->pos()))
{
this->curClickNum = i + this->curTopIndex;
this->onAnimChange(true);
break;
}
}
}
}
void SelfComboBox::mouseReleaseEvent(QMouseEvent *event)
{
if(this->curClickNum != -1)
{
this->setCurrentIndex(this->curClickNum);
this->setCurrentText(this->allData[this->curClickNum]);
this->focusOutEvent(nullptr);
}
this->onAnimChange(false);
}
void SelfComboBox::mouseMoveEvent(QMouseEvent *event)
{
// 这里必须使用buttons()
if(event->buttons() & Qt::LeftButton) //进行的按位与
{
this->curClickNum = -1;
for (int i = 0; i < this->curDrawNum; i++)
{
if(this->activePoints[i].contains(event->pos()))
{
this->curClickNum = i + this->curTopIndex;
update();
break;
}
}
}
}
void SelfComboBox::drawRoundedView(QPainter *painter)
{
if(this->curOpenNum <= 0)
{
return;
}
//为啥这里逻辑那么奇怪(主要是我先在这里面写完,再把变量提出去的……)
int maxNum = this->curOpenNum;
int drawNum = maxNum;
//数据条
int dataNum = this->count();//this->count();
//第一个起点
//Y偏移量
int bgOffect = 2;
double beginPosX = 2, beginPosY = itemHeight + this->viewSpace, itemWidth = width() - bgOffect * 2;
double bgBeginPosY = beginPosY;
if(dataNum <= maxNum)
{
maxNum = dataNum;
yOffect = 0;
}
curTopIndex = floor(yOffect * 1.0 / (itemHeight + space));
int curBottomIndex = curTopIndex + maxNum;
double topLeftNum = (yOffect * 1.0 / (itemHeight + space)) - curTopIndex;
curTopIndex = curTopIndex + maxNum >= dataNum ? dataNum - maxNum: curTopIndex;
topLeftNum = curTopIndex + maxNum >= dataNum ? 0.0: topLeftNum;
curBottomIndex = curTopIndex + maxNum;
if(topLeftNum > 0.0)
{
curBottomIndex += 1;
drawNum += 1;
}
if(yOffect >= (dataNum - maxNum) * (itemHeight + space))
{
yOffect = (dataNum - maxNum) * (itemHeight + space);
}
this->curDrawNum = drawNum;
beginPosY = beginPosY - topLeftNum * (itemHeight);
//画背景
painter->setPen(Qt::PenStyle::NoPen);
QBrush brush(Qt::BrushStyle::SolidPattern);
brush.setColor(QColor(15, 15, 15, 255 * 0.1));
painter->setBrush(brush);
painter->drawRoundedRect(beginPosX-bgOffect, bgBeginPosY+bgOffect, itemWidth+bgOffect * 2, itemHeight * maxNum + space * (maxNum - 1), this->roundRadius, this->roundRadius);
painter->setClipRegion(QRegion(beginPosX, bgBeginPosY, itemWidth, itemHeight * maxNum + space * (maxNum - 1)));
painter->setClipping(true);
//画小按钮
QPen pen(Qt::PenStyle::SolidLine);
pen.setJoinStyle(Qt::PenJoinStyle::RoundJoin);
pen.setColor(QColor(176, 211, 212, 255));
painter->setFont(font);
for(int index = 0; index < drawNum; index++)
{
brush.setColor(Qt::white);
painter->setBrush(brush);
painter->setPen(Qt::PenStyle::NoPen);
int beginY = beginPosY + ((itemHeight + space) * index);
QRect info_rect(beginPosX, beginY, itemWidth, itemHeight);
QPainterPath path;
path.setFillRule( Qt::WindingFill );
if(index == 0 || index == drawNum - 1)
{
if(index == 0)
{
info_rect = QRect(beginPosX, bgBeginPosY + ((itemHeight + space) * index), itemWidth, (1 - topLeftNum) * itemHeight);
// info_rect.setHeight((1 - topLeftNum) * itemHeight);
// info_rect.setTop(bgBeginPosY + ((itemHeight + space) * index));
path.addRoundedRect (info_rect, this->roundRadius, this->roundRadius);
QRect temp_rect(info_rect.left(), info_rect.top() + info_rect.height() / 2.0, info_rect.width(), info_rect.height() / 2.0 + 0.5);
path.addRect(temp_rect);
}
else
{
if(topLeftNum > 0.0) info_rect.setHeight(topLeftNum * itemHeight);
path.addRoundedRect (info_rect, this->roundRadius, this->roundRadius);
QRect temp_rect(info_rect.left(), info_rect.top(), info_rect.width(), info_rect.height()/2.0 + 1);
path.addRect(temp_rect);
}
}
else
{
path.addRect(info_rect);
}
// painter->drawRoundedRect(beginPosX, beginY, itemWidth, itemHeight, this->roundRadius, this->roundRadius);
if(curTopIndex + index == curClickNum)
{
QRadialGradient radial(beginPosX + itemWidth * 0.5, beginY + itemHeight, this->radius, beginPosX + itemWidth * 0.5, beginY);
if(this->radius > 1)
{
radial.setColorAt(0, QColor(124, 34, 235));
}
radial.setColorAt(1, Qt::white);
// 设置显示模式
radial.setSpread(QGradient::PadSpread);
// 设置画刷填充
painter->setBrush(radial);
// pen.setColor(QColor(0, 0, 0, 125));
}
else
{
painter->setBrush(Qt::white);
// pen.setColor(QColor(176, 211, 212, 255));
}
this->activePoints[index] = info_rect;
painter->drawPath(path);
painter->setPen(pen);
painter->drawText(beginPosX, beginY, itemWidth, itemHeight, Qt::AlignCenter, this->allData[index + curTopIndex]);
}
//这里!两种方法,第一种是设置绘制区域,第二种是注释的代码,擦拭掉多出来的区域
painter->setClipping(false);
// QRect destination(beginPosX, beginPosY, itemWidth, bgBeginPosY - beginPosY);
// painter->eraseRect(destination);
// QRect bottomDes(beginPosX, bgBeginPosY + (maxNum * space - 1) + (maxNum * itemHeight) + 1, itemWidth, itemHeight - (bgBeginPosY - beginPosY));
// painter->eraseRect(bottomDes);
//画顶部底部小三角
int triWidth = 15, triHeight = 8;
if(curTopIndex > 0 || topLeftNum > 0.0)
{
double topY = bgBeginPosY - triHeight;
QPoint first(beginPosX + itemWidth * 0.5 - triWidth * 0.5, bgBeginPosY);
QPoint second(beginPosX + itemWidth * 0.5 + triWidth * 0.5, bgBeginPosY);
QPoint third(beginPosX + itemWidth * 0.5, topY);
QPoint points[3] = {first, second, third};
painter->setPen(Qt::NoPen);
painter->drawPolygon(points, 3);
}
if(curBottomIndex < dataNum)
{
double bottomPosY = bgBeginPosY + (maxNum * space - 1) + (maxNum * itemHeight);
double topY = bottomPosY + triHeight;
painter->setPen(Qt::NoPen);
brush.setColor(QColor(15, 15, 15, 255 * 0.1));
painter->setBrush(brush);
QPoint bgFirst(beginPosX + itemWidth * 0.5 - triWidth * 0.5 - bgOffect, bottomPosY);
QPoint bgSecond(beginPosX + itemWidth * 0.5 + triWidth * 0.5 + bgOffect, bottomPosY);
QPoint bgThird(beginPosX + itemWidth * 0.5, topY + bgOffect);
QPoint bgPoints[3] = {bgFirst, bgSecond, bgThird};
painter->drawPolygon(bgPoints, 3);
brush.setColor(Qt::white);
painter->setBrush(brush);
QPoint first(beginPosX + itemWidth * 0.5 - triWidth * 0.5, bottomPosY - 1);
QPoint second(beginPosX + itemWidth * 0.5 + triWidth * 0.5, bottomPosY - 1);
QPoint third(beginPosX + itemWidth * 0.5, topY - 1);
QPoint points[3] = {first, second, third};
painter->drawPolygon(points, 3);
//重新设置Size
this->resize(width(), topY + bgOffect);
}
else
{
double bottomPosY = bgBeginPosY+bgOffect+itemHeight * maxNum + space * (maxNum - 1);
//重新设置Size
this->resize(width(), bottomPosY);
}
}
void SelfComboBox::drawRoundedViewTitle(QPainter *painter)
{
double beginPosX = 2, beginPosY = 0, itemWidth = width() - 4;
painter->setPen(Qt::PenStyle::NoPen);
QBrush brush(Qt::BrushStyle::SolidPattern);
brush.setColor(QColor(15, 15, 15, 255 * 0.1));
painter->setBrush(brush);
painter->drawRoundedRect(beginPosX-2, beginPosY+2, itemWidth + 4, itemHeight, this->roundRadius, this->roundRadius);
painter->setBrush(Qt::white);
painter->drawRoundedRect(beginPosX, beginPosY, itemWidth, itemHeight, this->roundRadius, this->roundRadius);
QPen pen(Qt::PenStyle::SolidLine);
pen.setJoinStyle(Qt::PenJoinStyle::RoundJoin);
pen.setColor(QColor(0, 0, 0, 125));
painter->setPen(pen);
painter->setFont(font);
QString firstText = this->count() > 0 ? this->allData[0] : "";
QString curText = this->currentText() == "" ? firstText : this->currentText();
painter->drawText(beginPosX, beginPosY, itemWidth, itemHeight, Qt::AlignCenter, curText);
//绘制三角形角标
int triWidth = 12, triHeight = 7, triHeightMove = 1;
int isDown = -1;
isDown = this->isViewOpen ? -1 : 1;
brush.setColor(Qt::white);
painter->setBrush(brush);
QPoint first(beginPosX + itemWidth * 0.8, beginPosY + itemHeight * 0.5 - triHeight * 0.5 * isDown + triHeightMove);
QPoint second(beginPosX + itemWidth * 0.8 + triWidth, beginPosY + itemHeight * 0.5 - triHeight * 0.5 * isDown + triHeightMove);
QPoint third(beginPosX + itemWidth * 0.8 + triWidth * 0.5, beginPosY + itemHeight * 0.5 + triHeight * 0.5 * isDown + triHeightMove);
QPainterPath painterPath;
painterPath.setFillRule( Qt::WindingFill );
painterPath.moveTo(first);
painterPath.lineTo(second);
painterPath.lineTo(third);
painter->fillPath(painterPath, QColor(176, 211, 212, 255));
}
小重点
主要是滚动的时候,怎么防止文字或者里面的东西在区域外显示:
1、先画出来在擦掉eraseRect
,缺点就是应用起来的时候会把别人的控件显示擦掉。
2、设置裁剪区域,这样就不用担心区域外的事了
painter->setClipRegion(QRegion(x, y, width, height));
painter->setClipping(true);
xxxxxx
painter->setClipping(false);
3、用图片混合模式搞QPainter::CompositionMode
,好麻烦,如果是做绘画,橡皮檫的可以用这个来实现(大概)
总结
最主要的还是在区域内绘制吧,其次为啥不用QSS,想加一点动画……(其实就是闲的),最后还是贴项目整体代码(瞎写的……)有个Bug……,count为0会报错,在drawRoundedView
加个判断。代码就不重新改重新上传了,见谅
void SelfComboBox::drawRoundedView(QPainter *painter)
{
if(this->curOpenNum <= 0)
{
return;
}
//这里!!!!!!!!!!!!!!!!!!!!!
if(this->count() <= 0)
{
return;
}
...
}