根据软件需求和设计的思路,提词器主体的部分应该就是一个无框的窗体,然后叠加一个qtextedit,利用qtextedit的滚动条功能,结合qtimer实现定时滚动的功能。
有个qt编写经验的同学其实知道,如果直接用ui建立widget的模式,实现无框窗体的话,设置qtextedit背景透明是没有问题的,关键是如果要实现窗体的移动和缩放就没有那么容易的了,你不能强制客户只在某一个位置点击鼠标然后移动和拖放,客户需要傻瓜化的操作,所以就必须考虑窗体边界的自动识别以及在窗体外围拖拉鼠标进行缩放,以及鼠标在窗体任意位置点击后都可以进行窗体移动的可能。
本章尽然是为了在crtl+c和crtl+v的基础上进行拼接开发,那就祭出大杀器,程序员的参考宝库,github.com,国内是gitee,不过很多资源是搬迁自github的,那就直接github吧。打开网页,搜索qt 无框窗体,当然你也可以直接搜索提词器或者英文的关键词,也有一个开源的qpromt,不过看着感觉不是太爽,所以就从最基础的开始。
搜索关键词:FramelessWindow,无框窗体,提词器等等,然后看带qt的,日期不要太远,然后最好有点forks的。然后就找到了一个。
看描述,qt 无边框窗口,支持边界缩放,ok,就他了,再次感谢下分享该资源的朋友。
不过进去看下,内容太简单了,有很多功能不是需要的,而且有些监听事件重复,编译运行下,好像功能有部分并没有实现,软件最贵的是借鉴别人的思路,里面窗体和边界缩放还是不错的,直接把这个作为基础,然后把不用的代码测试删除,只保留无框窗体painter部分和边界缩放部分代码。
透明窗体刷新和重绘部分
void Widget::resizeBackground(int w, int h, int round, int margin, QColor color)
{
//如果窗口高度与屏幕高度相同,设置全屏模式,取消白边与圆角
if(height() == screenHeight)
round = margin = 0;
m_bkImg = QImage(size(), QImage::Format::Format_ARGB32);
m_bkImg.fill(QColor(0,0,0,0));//背景透明 int r, int g, int b, int a
QPainter painter(&m_bkImg);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
QPainterPath painterPath;
painter.setPen(color);
painterPath.addRoundedRect(margin, margin, w - 2 * margin, h - 2 * margin, round, round);
painter.fillPath(painterPath, color);
for (int i = 0; i < margin; i++)
{
QPainterPath path;
path.setFillRule(Qt::WindingFill);
path.addRoundedRect(margin - i, margin - i, w - (margin - i) * 2, h - (margin - i) * 2, round, round);
color.setAlpha(static_cast((margin - i - 1) / (1.0 * margin) * 192));
QPen pen(color);
pen.setWidth(1);
painter.setPen(pen);
painter.drawPath(path);
}
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.drawImage(0, 0, m_bkImg.scaled(this->size()));
//qDebug()<< "paint event";
}
边界判断部分
void Widget::region(const QPoint &cursorGlobalPoint, bool &activeFlag)
{
QRect rect = this->rect();
QPoint tl = mapToGlobal(rect.topLeft());
QPoint rb = mapToGlobal(rect.bottomRight());
int x = cursorGlobalPoint.x();
int y = cursorGlobalPoint.y();
activeFlag = true;
if (tl.x() + PADDING + 2 >= x && tl.x() <= x && tl.y() + PADDING + 2 >= y && tl.y() <= y) {
m_dir = WMSZ_TOPLEFT;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
} else if (x >= rb.x() - PADDING - 2 && x <= rb.x() && y >= rb.y() - PADDING - 2 && y <= rb.y()) {
m_dir = WMSZ_BOTTOMRIGHT;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
} else if (x <= tl.x() + PADDING + 2 && x >= tl.x() && y >= rb.y() - PADDING - 2 && y <= rb.y()) {
m_dir = WMSZ_BOTTOMLEFT;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
} else if (x <= rb.x() && x >= rb.x() - PADDING - 2 && y >= tl.y() && y <= tl.y() + PADDING + 2) {
m_dir = WMSZ_TOPRIGHT;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
} else if (x <= tl.x() + PADDING && x >= tl.x()) {
m_dir = WMSZ_LEFT;
this->setCursor(QCursor(Qt::SizeHorCursor));
} else if (x <= rb.x() && x >= rb.x() - PADDING) {
m_dir = WMSZ_RIGHT;
this->setCursor(QCursor(Qt::SizeHorCursor));
} else if (y >= tl.y() && y <= tl.y() + PADDING){
m_dir = WMSZ_TOP;
this->setCursor(QCursor(Qt::SizeVerCursor));
} else if (y <= rb.y() && y >= rb.y() - PADDING) {
m_dir = WMSZ_BOTTOM;
this->setCursor(QCursor(Qt::SizeVerCursor));
} else {
m_dir = -1;
this->setCursor(QCursor(Qt::ArrowCursor));
activeFlag = false;
}
}
本地事件监听与鼠标事件反馈(savexmlsetting部分是后面添加的,实现窗体移动后保存参数,下次启动后窗体实现位置记忆功能)
bool Widget::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
Q_UNUSED(result)
if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG")
{
auto msg = reinterpret_cast(message);
if(msg->hwnd != reinterpret_cast(this->winId())) return false;
if (msg->message == WM_MOUSEMOVE){
bool activeFlag;
region(cursor().pos(), activeFlag);
}
else if (msg->message == WM_SIZING){
qDebug() << "wm sizing";
}
else if (msg->message == WM_MOVING){
qDebug() << "wm moving";
}
}
//qDebug() << "native event filter";
return false;
}
void Widget::mousePressEvent(QMouseEvent *event)
{
bool activeFlag;
region(cursor().pos(), activeFlag);
//qDebug() << "check left button " << activeFlag << m_dir;
if(event->button() == Qt::LeftButton){
if(::ReleaseCapture()){
if (activeFlag && m_dir > 0) {
::SendMessage(HWND(this->winId()), WM_SYSCOMMAND, SC_SIZE | m_dir, 0);
}
::SendMessage(HWND(this->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
QRect rect = this->rect();
QPoint tl = mapToGlobal(rect.topLeft());
posx = tl.x();
posy = tl.y();
savexmlsetting("posx",QString("%1").arg(posx));
savexmlsetting("posy",QString("%1").arg(posy));
}
//qDebug() << "check Left Button";
}
else if(event->button() == Qt::RightButton){
//qDebug() << "check Right Button";
}
else if(event->button() == Qt::MidButton){
//qDebug() << "check middle Button";
}
//qDebug() << "mousepressevent" << event->button();
__super::mousePressEvent(event);
}
窗体缩放后,根据缩放进行事件处理,原作者是重绘窗体,此处上面加了窗口缩放后尺寸大小的记忆功能
void Widget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
ticiW = width();
ticiH = height();
savexmlsetting("width",QString("%1").arg(ticiW));
savexmlsetting("height",QString("%1").arg(ticiH));
if(height() != screenHeight){
m_normalSize = size();
}
resizeBackground(width(), height(), 0, 0, QColor(backR,backG,backB,backA));//set your like
//qDebug() << "resizeEvent";
}
运行测试,一个可改变背景的透明窗体就ok了,大房子第一步,平面图搞定,就像一个画板,先确定布局,后面就可以在此界面上在进行后续的搭建和绘画。