在上个世纪的文章中,老周简单介绍了 QWindow 类的基本使用——包括从 QWindow 类派生和从 QRasterWindow 类派生。
其实,QWindow 类并不是只能充当主窗口用,它也可以嵌套到父级窗口中,变成子级对象。咱们一般称之为【控件】。F 话不多讲,下面咱们用实际案例来说明。
这个例子中老周定义了两个类:
class MyControl : public QRasterWindow
{
Q_OBJECT
public:
MyControl(QWindow *parent = nullptr);
private:
// “开启”状态时的背景色
QColor _on_bgcolor;
// “关闭”状态时的背景色
QColor _off_bgcolor;
// 当前状态
bool _state;
signals:
// 信号
void stateChanged(bool isOn);
public:
// 获取状态
inline bool state() const { return _state; }
// 修改状态
inline void setState(bool s)
{
_state = s;
// 发出信号
emit stateChanged(_state);
}
protected:
// 重写方法
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent* evebt) override;
};
在私有成员中,两个 QColor 类型的变量分别表示控件处于【开】或【关】状态时的背景色。_state 是一个布尔值,true就是【开】,false就是【关】,用来存储控件的当前状态。
公共方法 state 获取当前状态,setState 方法用来修改当前状态,同时会发出 stateChanged 信号。信号成员咱们先放一下,后文再叙。
两个虚函数的重写。paintEvent 负责画出控件的模样;mousePressEvent 当鼠标左键按下时改变控件的状态。实现点击一下开启,再点击一下关闭的功能。
接下来是实现各个成员。先是构造函数。
MyControl::MyControl(QWindow* parent)
:QRasterWindow::QRasterWindow(parent),
_state(false),
_on_bgcolor(QColor("red")),
_off_bgcolor(QColor("gray"))
{
}
构造函数主要用来初始化几个私有成员。下面代码实现鼠标左键按下后更改状态。
void MyControl::mousePressEvent(QMouseEvent *event)
{
if(! (event->buttons() & Qt::MouseButton::LeftButton))
return; // 如果按的不是左键就 PASS
this->setState(!this->state());
update();
}
每次点击后控件的状态都会取反(开变关,关变开),为了反映改变必须重新绘制控件,所以要调用 update 方法。
paintEvent 中实现绘制的过程。
void MyControl::paintEvent(QPaintEvent* event)
{
// 如果当前为不可见状态,就不绘图了
if(!isExposed())
return;
// 要绘制的区域
QRect rect = event -> rect();
QPainter painter;
painter.begin(this);
// 根据状态填充背景
QBrush bgbrush;
bgbrush.setStyle(Qt::SolidPattern);
if(_state)
{
bgbrush.setColor(_on_bgcolor);
}
else
{
bgbrush.setColor(_off_bgcolor);
}
painter.fillRect(rect, bgbrush);
QRect rectSq;
// 如果是“开”的状态,绿色矩形在右侧
if(_state)
{
rectSq.setX(rect.width() / 3 * 2);
rectSq.setWidth(rect.width() / 3);
}
// 如果为“关”的状态,绿色矩形在左侧
else{
rectSq.setWidth(rect.width() / 3);
}
rectSq.setHeight(rect.height());
painter.fillRect(rectSq, QColor("gold"));
painter.end();
}
绘制分两步走。第一步是填充背景矩形,如果【开】就填充红色,如果【关】就填充灰色。此处用到了 QBrush 对象。根据不同颜色调用 setColor 方法来设置。这里要注意 QBrush 对象要调用 setStyle 方法设置画刷样式为 SolidPattern。这是因为 QBrush 类默认的 style 是 NoBrush,因此要手动设置一下,不然看不到绘制。
第二步是绘制小方块。当控件状态为【开】时方块在右边,状态为【关】时方块在左边。小方块的宽度是控件宽度的 1/3,高度与控件相同。要显式调用 setHeight 方法设置矩形高度(因为它默认为0)。记得小方块的左上角的 X 坐标也要调整的。
下面是窗口 MyWindow 的成员。
class MyWindow : public QRasterWindow
{
Q_OBJECT
public:
MyWindow(QWindow *parent = nullptr);
private:
MyControl *_control;
void initUI();
protected:
void paintEvent(QPaintEvent *event) override;
};
重写的 paintEvent 方法负责画窗口的背景。私有成员 initUI 用于初始化子窗口(MyControl对象)。initUI 方法在构造函数中调用。
MyWindow::MyWindow(QWindow *parent)
:QRasterWindow::QRasterWindow(parent)
{
initUI();
}
void MyWindow::initUI()
{
// 初始化窗口
setTitle("示例程序");
resize(550, 450);
setMinimumSize(QSize(340, 200));
_control = new MyControl(this);
// 初始化子窗口
_control->setPosition(35, 40);
_control->resize(120, 35);
_control->setVisible(true);
}
setVisible 方法使用控件变为可见,只有可见的对象才能被看到。
paintEvent 方法只负责画窗口背景。
void MyWindow::paintEvent(QPaintEvent *event)
{
// 只填充窗口
QPainter painter(this);
painter.fillRect(event->rect(), QColor("blue"));
painter.end();
}
最后写 main 函数。
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
MyWindow wind;
wind.show();
return QGuiApplication::exec();
}
运行后,点一下子窗口,它就会改变颜色。

这个问题比较不确定,不同平台好像表现不一样。Windows 下在控件周围会多出白色区域(也可能是黑色);在 Debian 下没有出现白边。所以,为了尽可能地避免跨平台差异导致的问题,可以用 mask 让控件区域之外的地方变为透明,这样白边黑边就会消失。
调用 setMask 方法要在重写的 resizeEvent 方法中进行,这是为了响应控件大小改变后,及时调整要透明的区域。
void MyControl::resizeEvent(QResizeEvent *event)
{
setMask(QRect(QPoint(), event->size()));
}
刚才,咱们 MyControl 类定义了 stateChanged 信号,并在修改控件状态后发出。接下来我们用一个 lambda 表达式来充当 slot,当控件状态改变后输出调试信息(使用 qDebug 宏)。
connect(_control, &MyControl::stateChanged, this, [](bool st){
qDebug() << "当前状态:" << st;
});
再次运行程序,每点击一下控件,控制台就会输出一条调试信息。

setMask 还有一个经典用途——制作透明窗口。下面咱们顺便讨论一下如何做不规则形状的窗口。
a、调用 setFlags 方法设置 WindowType::FramelessWindowHint 标志,去掉窗口的边框;
b、调用 setMask 方法时需要传递一个 QRegion 对象,它表示一个形状区域。这个区域可以是矩形,也可以是圆形,当然也可以是多边形。设置 mask 后,指定区域外的内容变成透明,并且不会响应用户的输入事件(鼠标、键盘等)。QRegion 对象可以进行 and、or 等运算,多个区域可以进行交集或并集处理,形成各种不规则图形。
咱位做个例子。
#ifndef APP_H
#define APP_H
#include <QColor>
#include <QSize>
#include <QWindow>
#include <QRasterWindow>
#include <QPainter>
#include <QRect>
#include <QPaintEvent>
#include <QRegion>
#include <QList>
#include <QPolygon>
#include <QPoint>
#include <QResizeEvent>
class CustWindow : public QRasterWindow
{
Q_OBJECT
public:
CustWindow(QWindow *parent = nullptr);
protected:
void paintEvent(QPaintEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
// 窗口没了边框无法用常规操作,于是实现双击关闭窗口
void mouseDoubleClickEvent(QMouseEvent *event) override;
};
#endif
CustWindow::CustWindow(QWindow* parent)
:QRasterWindow::QRasterWindow(parent)
{
setFlags(Qt::WindowType::FramelessWindowHint|Qt::WindowType::BypassWindowManagerHint);
// 窗口位置和大小
setGeometry(600, 500, 400, 400);
}
void CustWindow::paintEvent(QPaintEvent* event)
{
QPainter p(this);
p.fillRect(event->rect(), QColor("deeppink"));
p.end();
}
void CustWindow::resizeEvent(QResizeEvent *event)
{
QRegion rg0(QRect(QPoint(), event->size()));
QList<QPoint> pt1 = {
{15,16},
{180, 300},
{320, 300},
{150, 57}
};
QPolygon pl1(pt1);
QRegion rg1(pl1);
QList<QPoint> pt2 = {
{315, 20},
{25, 160},
{160, 320},
{240, 150},
{330, 30}
};
QPolygon pl2(pt2);
QRegion rg2(pl2);
QRegion rg3 = rg0 & (rg1 | rg2);
setMask(rg3);
}
void CustWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
this->close(); // 关闭窗口
}
}
调用setFlags方法时,用到了 WindowType::BypassWindowManagerHint 值。Windows 下没有影响,主要是针对 X11,去除第三方主题产生的窗口阴影。
在 resize 事件处理代码中,QPolygon 类用来构建多边形,通过 QList<QPoint> 对象来指定顶点列表。上面代码中是两个不规则图形并集后,再与窗口的矩形区域取交集。最后把这个区域外的内容都设置成了透明。
运行效果如下。

我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib
这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[
下面例子中的Nested和Child有什么区别?是否只是同一事物的不同语法?classParentclassNested...endendclassChild 最佳答案 不,它们是不同的。嵌套:Computer之外的“Processor”类只能作为Computer::Processor访问。嵌套为内部类(namespace)提供上下文。对于ruby解释器Computer和Computer::Processor只是两个独立的类。classComputerclassProcessor#Tocreateanobjectforthisc
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
我有一个名为posts的模型,它有很多附件。附件模型使用回形针。我制作了一个用于创建附件的独立模型,效果很好,这是此处说明的View(https://github.com/thoughtbot/paperclip):@attachment,:html=>{:multipart=>true}do|form|%>posts中的嵌套表单如下所示:prohibitedthispostfrombeingsaved:@attachment,:html=>{:multipart=>true}do|at_form|%>附件记录已创建,但它是空的。文件未上传。同时,帖子已成功创建...有什么想法吗?
我真的为这个而疯狂。我一直在搜索答案并尝试我找到的所有内容,包括相关问题和stackoverflow上的答案,但仍然无法正常工作。我正在使用嵌套资源,但无法使表单正常工作。我总是遇到错误,例如没有路线匹配[PUT]"/galleries/1/photos"表格在这里:/galleries/1/photos/1/edit路线.rbresources:galleriesdoresources:photosendresources:galleriesresources:photos照片Controller.rbdefnew@gallery=Gallery.find(params[:galle
在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q
我需要根据字符串路径的长度将字符串路径数组转换为符号、哈希和数组的数组给定以下数组:array=["info","services","about/company","about/history/part1","about/history/part2"]我想生成以下输出,对不同级别进行分组,根据级别的结构混合使用符号和对象。产生以下输出:[:info,:services,about:[:company,history:[:part1,:part2]]]#altsyntax[:info,:services,{:about=>[:company,{:history=>[:part1,:pa
这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Testingmodulesinrspec目前我正在使用rspec成功测试我的模块,如下所示:require'spec_helper'moduleServicesmoduleAppServicedescribeAppServicedodescribe"authenticate"doit"shouldauthenticatetheuser"dopending"authenticatetheuser"endendendendend我的模块位于应用程序/服务/services.rb应用程序/服务/app_servi
关于这个有很多问题,但似乎都没有帮助。是的,我看过thisrailscast.我有一个作者,他有很多书,像这样:作者:classAuthor书:classBook我创建了以下表单以在authors#show中向作者添加一本书:#labelsandbuttons......使用以下authors_controller方法:defshow@author=Author.find(params[:id])@book=@author.books.buildend...以及以下books_controller方法:defcreate@author=Author.find(params[:autho