简介: 根据QStyle的继承关系和重绘原理;通过实现一个继承QCommonStyle类的实现,实现自己的自定义控件QSlider控件。

文章目录

  • 本博文的简述or解决问题?
  • 系列博文:
  • 运行效果:
  • QSlider属性:
  • 理解属性步长sigleStep、pageSteop:
  • 重绘QSlider:
    • 计算所需要的三个部分的矩形:
    • 再对其中每一个矩形(一共3个)进行重绘:
  • 若刻度异常情况(非bug):
  • 解决方法:
  • 思考总结:
  • 互联网分享:
  • 系列地址:

本博文的简述or解决问题?

详情: 见简介

编程环境: deepin 15.11 x64 专业版 Kernel: x86_64 Linux 4.15.0-30deepin-generic

编程软件: Qt Creator 4.8.2 (Enterprise)Qt 5.9.8

系列博文:

  • QStyle自定义重绘QSlider控件
  • QStyle之PenStyle的CustomDashLine使用 【更新:更加精准的绘画滑槽】
  • 重绘的QStyle中sizeFromContents()没有被调用
  • QStyle自定义重绘QSlider控件二 (重要)

运行效果:

先上一张最终的重绘运行效果图

运行代码,在 main() 函数里面:

QApplication app(argc, argv);qApp->setStyle("chameleon");QWidget wTemp;wTemp.resize(800, 600);//    NoTicks = 0,
//    TicksAbove = 1,
//    TicksLeft = TicksAbove,
//    TicksBelow = 2,
//    TicksRight = TicksBelow,
//    TicksBothSides = 3QHBoxLayout *layout = new QHBoxLayout(&wTemp);QSlider *slider1 = new QSlider(&wTemp);               //竖直刻度在右侧的Sliderslider1->setOrientation(Qt::Vertical);slider1->setTickPosition(QSlider::TicksRight);slider1->setTickInterval(10);slider1->resize(40, 300);QSlider *slider2 = new QSlider(&wTemp);              //竖直刻度在左侧的Sliderslider2->setOrientation(Qt::Vertical);slider2->setTickPosition(QSlider::TicksLeft);slider2->setTickInterval(10);slider2->resize(40, 300);slider2->move(150, 0);QSlider *slider3 = new QSlider(&wTemp);             //水平刻度在下侧的Sliderslider3->setOrientation(Qt::Horizontal);slider3->setTickPosition(QSlider::TicksBelow);slider3->resize(300, 40);slider3->setTickInterval(10);slider3->move(0, 400);QSlider *slider4 = new QSlider(&wTemp);             //水平刻度在上侧的Sliderslider4->setOrientation(Qt::Horizontal);slider4->setTickPosition(QSlider::TicksAbove);slider4->resize(300, 40);slider4->setTickInterval(10);slider4->move(400, 400);QSlider *slider5 = new QSlider(&wTemp);             //竖直刻度在两侧的Sliderslider5->setOrientation(Qt::Vertical);slider5->setTickPosition(QSlider::TicksBothSides);slider5->resize(40, 300);slider5->setTickInterval(10);slider5->move(300, 0);QSlider *slider6 = new QSlider(&wTemp);            //水平刻度在两侧的Sliderslider6->setOrientation(Qt::Horizontal);slider6->setTickPosition(QSlider::TicksBothSides);slider6->resize(300, 40);slider6->setTickInterval(10);slider6->move(0, 500);wTemp.show();return app.exec();

QSlider属性:

  • Qt文档:

先看Qt官方文档介绍,我搽,就这么两个属性???然后大致浏览完了整篇。怎么给我一点,怎么只有这么一点讲解的内容啊!!!这可不行,谁受得了啊 ,然后继续看源码,看其继承的基类QAbstractSlider等之后。这还差不多。不然我怎么使用QStyle重绘这个控件

  • tickInterval : int
    此属性保存tickmarks之间的间隔这是一个值间隔,而不是像素间隔。如果为0,滑块将在singleStep和pageStep之间进行选择。
  • tickPosition : TickPosition
    此属性保存此滑块的tickmark位置,有效值由QSlider::TickPosition enum描述。默认值是QSlider:: notice。

刻度条绘画方向:

Constant Description
QSlider::NoTicks 不显示刻度
QSlider::TicksBothSides 两边都显示刻度
QSlider::TicksAbove 刻度显示在上边(水平)
QSlider::TicksBelow 刻度显示在下边(水平)
QSlider::TicksLeft 刻度显示在左边(竖直)
QSlider::TicksRight 刻度显示在右边(竖直)

QSlider的摆放方向:

orientation : Qt::Orientation Description
Qt::Vertical 滑动条竖直绘画
Qt::Horizontal 滑动条水平绘画

其他几个涉及QStyle重绘的重要属性:

val 含义
minimum 滑动条最小值
maximum 滑动条最大值
singleStep 在min-max之间显示的步长,移动一步的改变数值
pageStep 用于计算刻度的个数(有阈值限制,不完全按这个来,没详细研究,柑橘用来有点迷);
value 当前的显示数值(在min-max之间,也是本信号的槽的参数的数值)
sliderPosition ??
tickInterval 两个刻度之间的间隔数值(重绘使用)
tickPosition 刻度的位置

注意: pageStep 这个用于计算刻度的个数(有阈值限制,不完全按这个来,没详细研究,柑橘用来有点迷); 刻度个数 - 1 = 刻度间隔的个数 = (span - 0) / pageStep; 关于0, span, min, max, val 的关系,参见sliderPositionFromValue()的实现 [此处特指qfusionstyle.cpp 里面的, 不知道qcommstyle.cpp实现原理是否相同?]

理解属性步长sigleStep、pageSteop:

因为QSlider = 滑块(句柄)+ 滑槽 + 刻度(矩形)

创建一个简单的小例子,核心代码如下:

void Widget::on_sliderHor_valueChanged(int value)
{int minimum = ui->sliderHor->minimum();                 //滑动条最小值int maximum = ui->sliderHor->maximum();                 //滑动条最大值int sigleStep = ui->sliderHor->singleStep();            //在min~max之间显示的步长,移动一步的改变数值int pageSteop = ui->sliderHor->pageStep();              //用于计算刻度的个数(有阈值限制,不完全按这个来)int val = ui->sliderHor->value();                      //当前的显示数值(在min~max之间,也是本信号的槽的参数的数值)int sliderPosition = ui->sliderHor->sliderPosition();   //?? 不是很清楚int tickinterval = ui->sliderHor->tickInterval();       //两个刻度之间的间隔数值(重绘使用)int tickPosition = ui->sliderHor->tickPosition();       //刻度的位置QString str = QString("value:%1,  minimum:%2,  maximum:%3,  sigleStep:%4,  pageSteop:%5,  val:%6,  sliderPosition:%7,  tickinterval:%8,  tickPosition:%9").arg(value).arg(minimum).arg(maximum).arg(sigleStep).arg(pageSteop).arg(val).arg(sliderPosition).arg(tickinterval).arg(tickPosition);qDebug()<<str;
}

其效果如下:有qDebug可以查看每一个数值的含义,以及变化可以看出其含义(每次按下按下一个方向→或←按键);就会显示一行数据, 重点观察sigleStep、sigleStep、pageSteop的数值;

感觉其上面这些矩形,大多是使用时候,而非更底层的重绘该空间使用的;而sliderPositiontickPosition才是重绘使用得。有了上面的基础之后,下面开始正经的重绘:

重绘QSlider:

重绘思路:

  • 先预先计算好滑块(矩形)+ 滑槽(矩形) + 刻度(矩形)
  • 对该部分矩形进行绘画

计算所需要的三个部分的矩形:

QRect CustomStyle::subControlRect(QStyle::ComplexControl cc, const QStyleOptionComplex *opt,QStyle::SubControl sc, const QWidget *w) const
{switch (cc) {case CC_Slider: {if (const QStyleOptionSlider *option = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {QRectF rect = option->rect;                                                    //Slider控件总的大小矩形int slider_size = proxy()->pixelMetric(PM_SliderControlThickness, opt, w);     //滑块的高度
//            int tick_size = proxy()->pixelMetric(PM_SliderTickmarkOffset, opt, w);         //刻度的高度QRectF slider_handle_rect = rect;                                              //滑块和滑漕的的最小公共矩形 (后面被用作临时且被改变的)if (option->orientation == Qt::Horizontal) {slider_handle_rect.setHeight(slider_size);if (option->tickPosition == QSlider::TicksAbove) slider_handle_rect.moveBottom(rect.bottom());if (option->tickPosition == QSlider::TicksBelow) slider_handle_rect.moveTop(rect.top());if (option->tickPosition == QSlider::TicksBothSides) slider_handle_rect.moveCenter(rect.center());} else {slider_handle_rect.setWidth(slider_size);if (option->tickPosition == QSlider::TicksRight)  slider_handle_rect.moveLeft(rect.left());if (option->tickPosition == QSlider::TicksLeft)   slider_handle_rect.moveRight(rect.right());if (option->tickPosition == QSlider::TicksBothSides) slider_handle_rect.moveCenter(rect.center());}QRectF rectStatic =  slider_handle_rect;   //rectStatic作为 滑块和滑漕的的最小公共矩形(不改变)switch (sc) {case SC_SliderGroove: {  //滑漕qreal groove_size = slider_size / 4.0;QRectF groove_rect;if (option->orientation == Qt::Horizontal) {groove_rect.setWidth(slider_handle_rect.width());groove_rect.setHeight(groove_size);} else {groove_rect.setWidth(groove_size);groove_rect.setHeight(slider_handle_rect.height());}groove_rect.moveCenter(slider_handle_rect.center());return groove_rect.toRect();}case SC_SliderHandle: {  //滑块int sliderPos = 0;int len = proxy()->pixelMetric(PM_SliderLength, option, w);bool horizontal = option->orientation == Qt::Horizontal;sliderPos = sliderPositionFromValue(option->minimum, option->maximum, option->sliderPosition,(horizontal ? slider_handle_rect.width() : slider_handle_rect.height()) - len, option->upsideDown);if (horizontal) {slider_handle_rect.moveLeft(slider_handle_rect.left() + sliderPos);slider_handle_rect.setWidth(len);slider_handle_rect.moveTop(rectStatic.top());} else {slider_handle_rect.moveTop(slider_handle_rect.top() + sliderPos);slider_handle_rect.setHeight(len);slider_handle_rect.moveLeft(rectStatic.left());}return slider_handle_rect.toRect();}case SC_SliderTickmarks: {  //刻度的矩形QRectF tick_rect = rect;if (option->orientation == Qt::Horizontal) {tick_rect.setHeight(rect.height() - slider_handle_rect.height());if (option->tickPosition == QSlider::TicksAbove) {tick_rect.moveTop(rect.top());} else if (option->tickPosition == QSlider::TicksBelow) {tick_rect.moveBottom(rect.bottom());}} else {tick_rect.setWidth(rect.width() - slider_handle_rect.width());if (option->tickPosition == QSlider::TicksLeft) {tick_rect.moveLeft(rect.left());} else if (option->tickPosition == QSlider::TicksRight) {tick_rect.moveRight(rect.right());}}return tick_rect.toRect();}default:break;}}break;}default:break;}return QCommonStyle::subControlRect(cc, opt, sc, w);
}

再对其中每一个矩形(一共3个)进行重绘:

void ChameleonStyle::drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex *opt,QPainter *p, const QWidget *w) const
{switch (cc) {case CC_Slider : {if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {//各个使用的矩形大小和位置QRectF rect = opt->rect;                                                                            //Slider控件最大的矩形(包含如下三个)QRectF rectHandle = proxy()->subControlRect(CC_Slider, opt, SC_SliderHandle, w);                    //滑块矩形QRectF rectSliderTickmarks = proxy()->subControlRect(CC_Slider, opt, SC_SliderTickmarks, w);        //刻度的矩形QRect rectGroove = proxy()->subControlRect(CC_Slider, opt, SC_SliderGroove, w);                     //滑槽的矩形//            qDebug()<<"____04_____Slider控件最大的矩形(包含如下三个):"<<rect<<"  滑块矩形:"<<rectHandle<<"  刻度的矩形:"<<rectSliderTickmarks<<"   滑槽的矩形:"<<rectGroove<<endl;//            //测试(保留不删)p->fillRect(rect, Qt::gray);p->fillRect(rectSliderTickmarks, Qt::blue);p->fillRect(rectGroove, Qt::red);p->fillRect(rectHandle, Qt::green);qDebug()<<"---rect:"<<rect<<"  rectHandle:"<<rectHandle<<"   rectSliderTickmarks:"<<rectSliderTickmarks<<"   rectGroove:"<<rectGroove;QPen pen;//绘画 滑槽(线)if (opt->subControls & SC_SliderGroove) {pen.setStyle(Qt::CustomDashLine);QVector<qreal> dashes;qreal space = 1.3;dashes << 0.1 << space;pen.setDashPattern(dashes);pen.setWidthF(3);pen.setColor(getColor(opt, QPalette::Highlight));p->setPen(pen);p->setRenderHint(QPainter::Antialiasing);if (slider->orientation == Qt::Horizontal) {p->drawLine(QPointF(rectGroove.left(), rectHandle.center().y()), QPointF(rectHandle.left(), rectHandle.center().y()));pen.setColor(getColor(opt, QPalette::Foreground));p->setPen(pen);p->drawLine(QPointF(rectGroove.right(), rectHandle.center().y()), QPointF(rectHandle.right(), rectHandle.center().y()));} else {p->drawLine(QPointF(rectGroove.center().x(), rectGroove.bottom()), QPointF(rectGroove.center().x(),  rectHandle.bottom()));pen.setColor(getColor(opt, QPalette::Foreground));p->setPen(pen);p->drawLine(QPointF(rectGroove.center().x(),  rectGroove.top()), QPointF(rectGroove.center().x(),  rectHandle.top()));}}//绘画 滑块if (opt->subControls & SC_SliderHandle) {pen.setStyle(Qt::SolidLine);p->setPen(Qt::NoPen);p->setBrush(getColor(opt, QPalette::Highlight));p->drawRoundedRect(rectHandle, DStyle::pixelMetric(DStyle::PM_FrameRadius), DStyle::pixelMetric(DStyle::PM_FrameRadius));}//绘画 刻度,绘画方式了参考qfusionstyle.cppif ((opt->subControls & SC_SliderTickmarks) && slider->tickInterval) {                                   //需要绘画刻度p->setPen(opt->palette.foreground().color());int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, w);  //可用空间int interval = slider->tickInterval;                                       //标记间隔
//                int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, opt, w);      //标记偏移
//                int ticks = slider->tickPosition;                                          //标记位置int v = slider->minimum;int len = proxy()->pixelMetric(PM_SliderLength, slider, w);while (v <= slider->maximum + 1) {                                          //此处不添加+1的话, 会少绘画一根线const int v_ = qMin(v, slider->maximum);int pos = sliderPositionFromValue(slider->minimum, slider->maximum, v_, available) + len / 2;if (slider->orientation == Qt::Horizontal) {if (slider->tickPosition == QSlider::TicksBothSides) {              //两侧都会绘画, 总的矩形-中心滑槽滑块最小公共矩形p->drawLine(pos, rect.top(), pos, rectHandle.top());p->drawLine(pos, rect.bottom(), pos, rectHandle.bottom());} else {p->drawLine(pos, rectSliderTickmarks.top(), pos, rectSliderTickmarks.bottom());}} else {if (slider->tickPosition == QSlider::TicksBothSides) {p->drawLine(rect.left(), pos, rectHandle.left(), pos);p->drawLine(rect.right(), pos, rectHandle.right(), pos);} else {p->drawLine(rectSliderTickmarks.left(), pos, rectSliderTickmarks.right(), pos);}}// in the case where maximum is max intint nextInterval = v + interval;if (nextInterval < v)break;v = nextInterval;}}}break;}default:break;}DStyle::drawComplexControl(cc, opt, p, w);
}

若刻度异常情况(非bug):

注意: 下图中是使用pen.setStyle(Qt::DotLine);来绘画的,上面的代码修改为了pen.setStyle(Qt::CustomDashLine);来绘画,所以会**(看到的滑槽)**略有不一样;

当显示区域比较小的时候,而刻度条的个数又比较多的时候(密密麻麻的那种),当超过某一阈值时候,系统会自动压缩显示,一个变成**“胖瘦相间隔”**,此时如果将该粗窗口放大,则会被重绘画显示正常,刻度条均匀相间隔。

原图效果:

本来还以为是自己重新绘画的效果造成的,后面确定是过密形成的显示异常情况.就想着有没有能够的解决方法,能够重新绘画显示.想着参考QFusion 风格的实现,于是翻看源码,只看绘画刻度部分的Qt源码:

if (option->subControls & SC_SliderTickmarks) {painter->setPen(outline);int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget);int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);int interval = slider->tickInterval;if (interval <= 0) {interval = slider->singleStep;if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,available)- QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,0, available) < 3)interval = slider->pageStep;}if (interval <= 0)interval = 1;int v = slider->minimum;int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);while (v <= slider->maximum + 1) {if (v == slider->maximum + 1 && interval == 1)break;const int v_ = qMin(v, slider->maximum);int pos = sliderPositionFromValue(slider->minimum, slider->maximum,v_, (horizontal? slider->rect.width(): slider->rect.height()) - len,slider->upsideDown) + len / 2;int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0);if (horizontal) {if (ticksAbove) {painter->drawLine(pos, slider->rect.top() + extra,pos, slider->rect.top() + tickSize);}if (ticksBelow) {painter->drawLine(pos, slider->rect.bottom() - extra,pos, slider->rect.bottom() - tickSize);}} else {if (ticksAbove) {painter->drawLine(slider->rect.left() + extra, pos,slider->rect.left() + tickSize, pos);}if (ticksBelow) {painter->drawLine(slider->rect.right() - extra, pos,slider->rect.right() - tickSize, pos);}}// in the case where maximum is max intint nextInterval = v + interval;if (nextInterval < v)break;v = nextInterval;}}

等等, 这根本没有考虑这个问题,好不好。而且感觉绘画刻度的方法,是对一个矩形矩形多次精确的计算,eeemmmmmmmmmm,这样子是不是有点复杂了了。感觉没有我的将这一个超大矩形,分割成为三个小矩形这一思路简单,然后在对每一块小矩形进行相应的绘画。

解决方法:

决定尝试一下其原生**QFusion风格()是怎么解决这个效果,当设置刻度间隔比较小的时候,显示宽度比较窄时候,这个东西出现了"胖瘦相互间隔",然后利用上面的一开始创建的小例子,改变步长和刻度间个和max值,看看效果**

甚至再次改变步长和刻度间个和max值,看看效果。过于密集,成了线

握草,握了个草,我握了个大草.hhhhhhhhhhhhhhhhhh,原来老哥你也没有设置这个问题啊,果断的出结论,这不是bug.果断不再继续修改了重绘画了.坏坏的笑了几下之后。

于是将一开始出现问题的地方,将该窗口最大化,然后局部拉大,看到这个效果(果然得到了验证):

最后总结:

这个不是bug,或者显示异常。冷静下来是思考:如此窄的矩形里面,显示如此多根刻度线,由于像素限制,只能后绘画的比较密集,当这个数值更大的时候,会发现,这个会变成一条直线(放大拉开显示,才会显示其实是均匀相间隔的).

思考总结:

  • 首先检查代码,是否是相关部分的代码逻辑有问题
  • 仍然觉得不应该之后,试一下Qt自带的是否会重现,排除是自己还是非自己原因
  • 改变相关的值,写小例子验证,查看效果
  • 发现经验:+1; 完美结束

互联网分享:

因为有着许许多多的热心网友的无私分享,从他们的博客中学习成长,学会很多,故也不辞辛苦也将自己的项目或经验整理成博客的形式,也提供给一起大家学习探讨与交流

系列地址:

QtExamples

欢迎 starfork 这个系列的 QT / DTK 学习,附学习由浅入深的目录。

`QStyle`自定义重绘`QSlider`控件相关推荐

  1. QStyle自定义重绘QRubberBand控件

    简介: 根据QStyle的继承关系和重绘原理:通过实现一个继承QCommonStyle类的实现,实现自己的自定义控件QRubberBand控件. 文章目录 本博文的简述or解决问题? 运行效果: QR ...

  2. QStyle自定义重绘QScrollBar

    简介: QStyle自定义重绘QScrollBar(滚动条)样式. 文章目录 本博文的简述or解决问题? 运行效果: QScrollbar官方教程: QScrollbar属性理解: 系列地址: 本博文 ...

  3. c#-winform重绘Tabcontrol控件,标签带Logo图标

    模仿网页浏览器标签重绘Tabcontrol控件,每个标签页左上角的Logo图标可以自定义,当然图标也可以挪到右边,直接上图.

  4. 重绘MenuStrip 控件

    重绘MenuStrip控件 效果如图: 首先添加 CustomProfessionalRenderer类 用于重绘控件菜单样式 /// <summary>/// 自定义MenuStrip ...

  5. android 父控件的背景_android控件拖动,移动、解决父布局重绘时控件回到原点

    /** * * @description 设置意见反馈,用以灰度发布 * @author zhongwr * @params * @update 2016年1月12日 下午5:36:07 */ pri ...

  6. android控件拖动,移动、解决父布局重绘时控件回到原点

    最近公司做小游戏开发,写倒计时加进度条 拖动问题private boolean isIntercept = false; /** 按下时的位置控件相对屏幕左上角的位置X */ private int ...

  7. 重绘CButton控件

    1,创建一个类 继承自:CButtonl; #pragma once// CMyButtonclass CMyButton : public CButton {DECLARE_DYNAMIC(CMyB ...

  8. C#重绘TabControl控件的源码(转)

    代码   1using System;   2 using System.Collections.Generic;   3 using System.ComponentModel;   4 using ...

  9. 继承QWidget使用QPainter自定义二维图形控件【Qt学习】

    继承QWidget使用QPainter自定义二维图形控件[Qt学习] 通过阅读该文章,将了解本文所说的二维图形控件的基本概念.为何要自定义二维图形控件.如何自定义二维图形控件. 该文章将首先进行一些书 ...

最新文章

  1. MatConvnet工具箱文档翻译理解(3)
  2. multinorm r语言_与心理学数据分析相关的R工具包
  3. Pseudoprime numbers POJ - 3641(快速幂+判素数)
  4. 如何正确运用计算机技术,如何正确认识与运用计算机网络技术.doc
  5. 前端学习(1705):前端系列javascript之原型中的this
  6. 铁路系统的在线检测系统讲座
  7. mysql mmm vip_MySQL MMM架构看不到vip地址
  8. 实用的摩斯编码(二)
  9. tomcat7不支持html5吗,HTTP状态404说明请求的资源()不可用。的Apache Tomcat/7.0.27
  10. 数学建模——01规划 / 线性规划 (工具:matlab + lingo,算法:Folyd)
  11. 我的世界可以在服务器用修改器,我的世界TMI修改器使用教程
  12. 一般描绘性形容词_英语中描绘性形容词是指哪种形容词?
  13. Unity中下载图片、音频和视频
  14. 团体程序设计天梯赛——L1-039 古风排版
  15. iocomp ActiveX/VCL各版本大比拼之Crack
  16. 青岛新媒体运营教程:短视频没脚本怎么行,镜头脚本模板直接套用
  17. 计算机诞生历史小故事
  18. 城市隧道水位监测与预警系统
  19. Python遍历多个列表:ValueError: too many values to unpack (expected 2)
  20. Spring Cloud教程(十二)加密和解密

热门文章

  1. Kakaotalk被禁,kakao安装包,Kakaotalk下载不了,Kakaotalk国内不能用了怎么解决,怎么下载kakao,kakaotalk下载
  2. 32位计算机内存最大多少,32位系统到底支持多大的内存呢
  3. 双屏Android NDS模拟器,强悍的NDS模拟器《DraStic》移植到安卓
  4. 【转】如何下载国外的一些网盘(附软件)
  5. VM下虚拟机连接usb相机后电脑蓝屏重启问题
  6. mybatis使用范例并且和spring进行整合
  7. iwebshop 详解
  8. 最优加权最小二乘估计
  9. 【电子产品】ThinkPad 黑将S5 尺寸
  10. 【WEB】aspcms漏洞利用