自定义的Qt日期选择控件

此控件也能作为日历控件使用。实现了Windows系统日历控件的鼠标悬停有白色渐变的效果(见于下图18日周围的白色渐变效果)。从中可以学习到Qt日期类的常用方法,和渐变画刷的使用。作者的审美不行,不能很好地美化界面,所以界面不太好看。控件里的QSpinBox也没有用样式表修改外观使其与整体风格保持一致。此控件在VS2015和Qt5.9上测试通过。下面是效果图:

上代码,头文件:

class QSpinBox;

class MDatePicker : public QWidget
{
    Q_OBJECT

public:
    MDatePicker(QWidget* parent = 0);
    void setCurrentDate(const QDate& idate);
    QDate currentDate() const;

signals:
    void dateChanged(const QDate&);

private slots:
    void sbDateValueChanged();

private:
    void paintEvent(QPaintEvent *event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void enterEvent(QEvent *event) override;
    void leaveEvent(QEvent *event) override;
    void calcFillDayRect(qreal cellw, qreal cellh); /* 填充dayInfos */
    void drawHoverEffect(QPainter* painter);
    QRectF getBorderRect(const QRectF& cell);
    QRectF getFilledRect(const QRectF& cell);

    struct DayInfo
    {
        QRectF rect;
        QDate d;
    };

private:
    static const QMargins margin; /* 日期表格到控件的边距 */
    static const int texth; /* 周一到周日文本高度 */
    QDate date;
    QPoint hover;
    QPoint pressPt;
    QVarLengthArray<DayInfo, 42> dayInfos;
    QSpinBox* sbYear;
    QSpinBox* sbMonth;
};

CPP文件:

const QMargins MDatePicker::margin(6, 32, 6, 6);
const int MDatePicker::texth = 24;

MDatePicker::MDatePicker(QWidget* parent) :
    QWidget(parent), date(QDate::currentDate())
{
    setMouseTracking(true);

    QHBoxLayout* lay = new QHBoxLayout(this);
    lay->setContentsMargins(6, 6, 6, 6);
    lay->setAlignment(Qt::AlignTop);
    sbYear = new QSpinBox(this);
    sbYear->setMinimumWidth(70);
    sbYear->setMinimum(1900);
    sbYear->setMaximum(2100);
    sbYear->setValue(date.year());
    lay->addWidget(sbYear);
    QLabel* lbYear = new QLabel(u8"", this);
    lay->addWidget(lbYear);
    sbMonth = new QSpinBox(this);
    sbMonth->setMinimumWidth(70);
    sbMonth->setMinimum(1);
    sbMonth->setMaximum(12);
    sbMonth->setValue(date.month());
    lay->addWidget(sbMonth);
    QLabel* lbMonth = new QLabel(u8"", this);
    lay->addWidget(lbMonth);
    QSpacerItem* spacer = new QSpacerItem(24, 12, QSizePolicy::Expanding);
    lay->addSpacerItem(spacer);
    setLayout(lay);
    connect(sbYear, QOverload<int>::of(&QSpinBox::valueChanged), this, &MDatePicker::sbDateValueChanged);
    connect(sbMonth, QOverload<int>::of(&QSpinBox::valueChanged), this, &MDatePicker::sbDateValueChanged);
}

void MDatePicker::setCurrentDate(const QDate& idate)
{
    date = idate;
    update();
}

QDate MDatePicker::currentDate() const
{
    return date;
}

void MDatePicker::sbDateValueChanged()
{
    int year = sbYear->value();
    int month = sbMonth->value();
    date.setDate(year, month, 1);
    emit dateChanged(date);
    update();
}

inline QRectF MDatePicker::getBorderRect(const QRectF& cell)
{
    return cell.adjusted(2, 2, -2, -2);
}

inline QRectF MDatePicker::getFilledRect(const QRectF& cell)
{
    return cell.adjusted(4, 4, -4, -4);
}

void MDatePicker::calcFillDayRect(qreal cellw, qreal cellh)
{
    dayInfos.clear();
    for (int i = 0; i < 6; i++)
    {
        for (int j = 0; j < 7; j++)
        {
            qreal startx = margin.left() + j * cellw;
            qreal starty = margin.top() + texth + i * cellh;
            dayInfos.append({ QRectF(startx, starty, cellw, cellh), QDate() });
        }
    }

    int dayCount = date.daysInMonth();
    QDate firstDay = QDate(date.year(), date.month(), 1);
    int currMonthBegin = firstDay.dayOfWeek() - 1;
    for (int i = currMonthBegin; i < currMonthBegin + dayCount; i++)
    {
        dayInfos[i].d = firstDay.addDays(i - currMonthBegin);
    }
    int prevMonthDay = 1;
    for (int i = currMonthBegin - 1; i >= 0; i--)
    {
        dayInfos[i].d = firstDay.addDays(-prevMonthDay);
        prevMonthDay++;
    }
    int nextMonthDay = 0;
    for (int i = currMonthBegin + dayCount; i < 42; i++)
    {
        dayInfos[i].d = firstDay.addMonths(1).addDays(nextMonthDay);
        nextMonthDay++;
    }
}

void MDatePicker::drawHoverEffect(QPainter* painter)
{
    QRadialGradient gradient;
    gradient.setFocalPoint(hover);
    gradient.setFocalRadius(0);
    gradient.setCenter(hover);
    gradient.setRadius(100);
    gradient.setColorAt(0, QColor(255, 255, 255, 173));
    gradient.setColorAt(1, QColor(255, 255, 255, 0));
    painter->setPen(QPen(gradient, 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin));
    painter->setBrush(Qt::NoBrush);
    for (auto& item : dayInfos)
    {
        painter->drawRect(getBorderRect(item.rect));
    }

    auto hit = std::find_if(dayInfos.begin(), dayInfos.end(),
        [this](const DayInfo& x) { return x.rect.contains(hover); });
    if (hit != dayInfos.end())
    {
        painter->setPen(QPen(QColor(255, 255, 255), 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin));
        painter->drawRect(getBorderRect(hit->rect));
    }
}

void MDatePicker::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    int tableWidth = width() - margin.left() - margin.right();
    int tableHeight = height() - margin.top() - texth - margin.bottom();
    qreal cellWidth = tableWidth / 7.0;
    qreal cellHeight = tableHeight / 6.0;
    painter.fillRect(margin.left(), margin.top(), tableWidth, texth, QColor(0x36, 0x4f, 0x6b));
    painter.fillRect(margin.left(), margin.top() + texth, tableWidth, tableHeight, QColor(0xe8, 0xe8, 0xe8));
    const QString weekdays[] = { u8"", u8"", u8"", u8"", u8"", u8"", u8"" };
    painter.setPen(Qt::white);
    for (int i = 0; i < 7; i++)
    {
        qreal start = margin.left() + i * cellWidth;
        QTextOption option;
        option.setAlignment(Qt::AlignCenter);
        painter.drawText(QRectF(start, margin.top(), cellWidth, texth), weekdays[i], option);
    }

    /* 计算前界面42个日期的信息 */
    calcFillDayRect(cellWidth, cellHeight);

    /* 绘制鼠标悬停渐变特效 */
    if (!hover.isNull())
    {
        drawHoverEffect(&painter);
    }

    /* 给当前日期绘制背景 */
    auto found = std::find_if(dayInfos.begin(), dayInfos.end(),
        [this](const DayInfo& x) { return x.d.month() == date.month() && x.d.day() == date.day(); });
    if (found != dayInfos.end())
    {
        painter.fillRect(getFilledRect(found->rect), QColor(0xfc, 0x51, 0x85));
    }
    /* 绘制日期数字 */
    for (auto& item : dayInfos)
    {
        painter.setPen((item.d.month() == date.month()) ? Qt::black : Qt::gray);
        QTextOption option;
        option.setAlignment(Qt::AlignCenter);
        painter.drawText(item.rect, QString::number(item.d.day()), option);
    }
}

void MDatePicker::mouseMoveEvent(QMouseEvent* event)
{
    hover = event->pos();
    update();
}

void MDatePicker::mousePressEvent(QMouseEvent *event)
{
    pressPt = event->pos();
}

void MDatePicker::mouseReleaseEvent(QMouseEvent *event)
{
    if (pressPt == event->pos())
    {
        auto clicked = std::find_if(dayInfos.begin(), dayInfos.end(),
            [this](const DayInfo& x) { return x.rect.contains(pressPt); });
        if (clicked != dayInfos.end())
        {
            date = clicked->d;
            sbYear->blockSignals(true);
            sbYear->setValue(date.year());
            sbYear->blockSignals(false);
            sbMonth->blockSignals(true);
            sbMonth->setValue(date.month());
            sbMonth->blockSignals(false);
            emit dateChanged(date);
            update();
        }
    }
}

void MDatePicker::enterEvent(QEvent *event)
{
    // nothing.
}

void MDatePicker::leaveEvent(QEvent *event)
{
    hover = QPoint();
    update();
}

 

posted @ 2022-11-29 20:10  兜尼完  阅读(1592)  评论(0编辑  收藏  举报