在Qt框架中,QSize类就像裁缝手中的软尺,是处理二维尺寸数据的核心工具。这个不起眼的类实际上支撑着整个Qt界面布局系统的基石,从按钮大小到窗口尺寸,无处不在发挥着关键作用。
QSize本质上是一个值类型(value type),这意味着它的行为更接近int、double这样的基本数据类型,而不是QObject这样的引用类型。这种设计带来几个重要特性:
构造函数提供了多种初始化方式:
cpp复制QSize(); // 构造空尺寸(0,0)
QSize(int width, int height); // 指定宽高构造
QSize(const QSize &other); // 拷贝构造
实际开发中最常用的成员变量当然是width和height,但要注意它们都是int类型。这在处理高DPI屏幕时可能成为陷阱——我们后面会专门讨论这个现代GUI开发中的常见痛点。
QSize提供的方法就像瑞士军刀一样丰富实用。先看最基础的尺寸调整:
cpp复制QSize size(100, 200);
size.scale(150, 150, Qt::KeepAspectRatio); // 调整为150x300
scale()方法的第三个参数特别重要,它决定了缩放策略:
实测发现一个有趣现象:当使用KeepAspectRatio模式时,Qt内部会先计算宽高比,然后选择较小的一边作为基准。这在实现图片缩略图功能时特别有用。
QSize重载了各种数学运算符,但有些行为可能出乎意料:
cpp复制QSize s1(100, 200);
QSize s2(50, 50);
QSize s3 = s1 + s2; // (150, 250)
QSize s4 = s1 - s2; // (50, 150)
QSize s5 = s1 * 2; // (200, 400)
QSize s6 = s1 / 2; // (50, 100)
警告:乘法运算只支持乘以整数/浮点数,不支持两个QSize相乘。这种设计是为了避免语义歧义——尺寸相乘应该得到面积吗?Qt选择不回答这个问题。
一个经常被忽视但极其重要的特性是QSize对无效状态的处理:
cpp复制QSize invalidSize;
invalidSize.setWidth(-1); // 标记为无效
if(invalidSize.isValid()) {
// 这里不会执行
}
无效尺寸在Qt内部有特殊用途,比如:
现代4K/5K显示器带来的高DPI挑战让QSize的使用变得复杂。看这个典型错误案例:
cpp复制QSize buttonSize(100, 50);
button->resize(buttonSize); // 在200%缩放屏上会显示过小
解决方案是使用QScreen的devicePixelRatio:
cpp复制qreal dpr = window()->screen()->devicePixelRatio();
QSize realSize = size * dpr;
但更Qt风格的做法是使用QSizeF(浮点版本)配合布局系统自动适配。这里有个性能取舍:QSizeF计算开销略大,但能获得更精确的布局效果。
在处理不同单位时,我总结了一套实用模式:
cpp复制// 毫米转像素
QSize mmToPx(const QSize &mmSize, const QPaintDevice *device) {
const qreal inchesPerMm = 0.0393701;
const int dpi = device->logicalDpiY(); // 通常使用Y轴DPI
return QSize(mmSize.width() * inchesPerMm * dpi,
mmSize.height() * inchesPerMm * dpi);
}
这个方法在打印预览、CAD类应用中特别有用。注意不同设备的logicalDpi可能不同,这是很多跨平台应用的坑点。
QSize的内存布局经过精心设计:
cpp复制struct QSize {
int wd;
int ht;
};
这种紧凑结构带来几个优势:
但要注意:在Qt 5.15后,Debug模式会加入边界检查。这意味着在性能关键路径上,Release模式的QSize操作可能快10倍以上。
在处理大量尺寸计算时(如图形编辑器),这些技巧很关键:
这里有个实测数据:在100万次尺寸计算的测试中,优化后的代码能快2-3倍。
让我们用QSize实现一个智能图片布局系统:
cpp复制class SmartLayout {
public:
void addImage(const QSize &imgSize) {
m_images.append(imgSize);
m_totalArea += imgSize.width() * imgSize.height();
}
QSize calculateBestLayout() const {
if(m_images.isEmpty()) return QSize();
qreal avgRatio = 0;
for(const auto &size : m_images) {
avgRatio += qreal(size.width()) / size.height();
}
avgRatio /= m_images.size();
int base = qSqrt(m_totalArea / avgRatio);
return QSize(base * avgRatio, base);
}
private:
QVector<QSize> m_images;
int m_totalArea = 0;
};
这个算法考虑了:
在实际图片墙应用中,这种布局比简单网格排列美观得多。
Qt 6对QSize做了一些微妙但重要的改变:
迁移时特别注意:如果你在Qt5中这样写:
cpp复制size.rwidth() = newWidth; // Qt6中会编译失败
应该改为:
cpp复制size.setWidth(newWidth);
Qt默认的QDebug输出很简洁:
cpp复制qDebug() << size; // 输出"QSize(100, 200)"
但调试复杂布局时,我推荐自定义输出格式:
cpp复制qDebug().nospace() << "[" << size.width() << "x"
<< size.height() << "@"
<< (size.width()/qreal(size.height())) << "]";
// 输出:[100x200@0.5]
cpp复制QSize size(100, 200);
size += QSize(-150, 0); // width变为-50
if(size.isEmpty()) { ... } // 判断会失败!
isEmpty()只在宽或高<=0时返回true,而isValid()要求两者都>=0
cpp复制qreal ratio = size.width() / size.height(); // 整数除法!
应该改为:
cpp复制qreal ratio = size.width() / qreal(size.height());
cpp复制size.transpose(); // 原地修改
QSize newSize = size.transposed(); // 返回新对象
QSize与QSizePolicy配合可以实现智能布局:
cpp复制QWidget *createSmartWidget() {
QWidget *widget = new QWidget;
QSizePolicy policy = widget->sizePolicy();
policy.setHorizontalStretch(2);
policy.setVerticalStretch(1);
policy.setHeightForWidth(true);
widget->setSizePolicy(policy);
return widget;
}
这种设置表示:
在复杂表单中,这种策略能大幅减少手动resize的代码量。
QSize与QVariant的交互有些微妙之处:
cpp复制QSize size(100, 200);
QVariant var = QVariant::fromValue(size);
// 类型转换检查
if(var.canConvert<QSizeF>()) { // 总是返回true
QSizeF sizeF = var.value<QSizeF>();
}
有趣的是,QMetaType系统将QSize和QSizeF视为可互相转换的类型。这在处理QML属性时会产生意外行为——QML中的所有尺寸都是QSizeF。
经过多年Qt开发,我总结出这些QSize黄金法则:
选择合适类型:
API使用原则:
性能关键点:
高DPI适配:
调试技巧:
这些经验来自实际项目中的教训。比如在某医疗影像项目中,我们因为混用逻辑/物理像素导致打印尺寸错误,差点造成医疗事故。从此我养成了在代码中加入明确单位注释的习惯:
cpp复制QSize displaySize(800, 600); // 逻辑像素
QSize printSize(mmToPx(QSize(100, 150), printer)); // 物理毫米转像素