“深入查询”示例
“钻取”示例演示了如何使用QSqlRelationalTableModel 和QDataWidgetMapper 类从数据库读取数据并提交更改。

运行示例应用程序时,用户可以通过点击相应的图片来检索每个项目的信息。应用程序会弹出一个显示相关数据的信息窗口,并允许用户修改描述和图片。当用户提交更改后,主视图将随之更新。
该示例由三个类组成:
ImageItem是一个用于显示图片的自定义图形项类。View是主应用程序控件,允许用户浏览各种项目。InformationWindow用于显示请求的信息,允许用户对其进行修改并将更改提交至数据库。
我们将首先了解InformationWindow 类,以了解如何从数据库中读取和修改数据。然后,我们将回顾主应用程序控件,即View 类,以及相关的ImageItem 类。
InformationWindow 类定义
InformationWindow 类是一个继承自QWidget 的自定义控件:
class InformationWindow : public QDialog
{
Q_OBJECT
public:
InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent = nullptr);
int id() const;
Q_SIGNALS:
void imageChanged(int id, const QString &fileName);创建信息窗口时,需向构造函数传入关联的项 ID、模型指针以及父窗口。我们将使用模型指针为窗口填充数据,并将父窗口参数传递给基类。ID 会被存储以备后续引用。
窗口创建后,每当需要查询指定位置的信息时,我们将使用公共函数 `id() ` 来定位该窗口。此外,当用户将更改提交至数据库时,我们还将利用该 ID 更新主应用程序控件,即每当用户更改关联图片时,我们会发出一个信号,其中包含 ID 和文件名作为参数。
private Q_SLOTS:
void revert();
void submit();
void enableButtons(bool enable);由于允许用户修改部分数据,我们必须提供撤销和提交更改的功能。enableButtons() 插槽是为了方便在需要时启用或禁用各个按钮而提供的。
private:
void createButtons();
int itemId;
QString displayedImage;
QComboBox *imageFileEditor = nullptr;
QLabel *itemText = nullptr;
QTextEdit *descriptionEditor = nullptr;
QPushButton *closeButton = nullptr;
QPushButton *submitButton = nullptr;
QPushButton *revertButton = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QDataWidgetMapper *mapper = nullptr;
};createButtons() 函数同样是一个便捷函数,旨在简化构造函数。如上所述,我们存储了项目 ID 以备将来参考。我们还存储了当前显示的图像文件名称,以便确定何时发出imageChanged() 信号。
信息窗口使用 `QLabel ` 类来显示项目的名称。关联的图像文件通过 `QComboBox ` 实例显示,而描述则通过 `QTextEdit` 显示。此外,该窗口还有三个按钮,用于控制数据流以及窗口是否显示。
最后,我们声明了一个映射器。QDataWidgetMapper 类负责将数据模型的某个部分与控件进行映射。我们将使用该映射器从给定的数据库中提取数据,并在用户修改数据时更新数据库。
InformationWindow 类的实现
构造函数接受三个参数:项目 ID、数据库指针和父控件。数据库指针实际上是指向一个 `QSqlRelationalTableModel ` 对象的指针,该对象为我们的数据库表提供了一个可编辑的数据模型(支持外键)。
InformationWindow::InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent)
: QDialog(parent)
{
QLabel *itemLabel = new QLabel(tr("Item:"));
QLabel *descriptionLabel = new QLabel(tr("Description:"));
QLabel *imageFileLabel = new QLabel(tr("Image file:"));
createButtons();
itemText = new QLabel;
descriptionEditor = new QTextEdit;首先,我们创建显示数据库中数据所需的各种控件。大多数控件的创建方式都很简单。但请注意显示图像文件名的下拉列表框:
imageFileEditor = new QComboBox;
imageFileEditor->setModel(items->relationModel(1));
imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));在此示例中,关于项目的信息存储在名为“items”的数据库表中。在创建模型时,我们将使用外键在该表与第二个数据库表“images”之间建立关系,后者包含可用图像文件的名称。关于具体实现方法,我们将在回顾View 类时再作说明。 建立这种关系的理由在于,我们希望确保用户只能从预定义的图片文件中进行选择。
与“images”数据库表对应的模型,可通过QSqlRelationalTableModel 的relationModel()函数获取,该函数需要外键(在本例中即“imagefile”列的编号)作为参数。 我们使用QComboBox 的setModel()函数,使下拉列表使用“images”模型。此外,由于该模型有两列(“itemid”和“file”),我们还通过QComboBox::setModelColumn()函数指定要显示的列。
mapper = new QDataWidgetMapper(this);
mapper->setModel(items);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
mapper->setItemDelegate(new QSqlRelationalDelegate(mapper));
mapper->addMapping(imageFileEditor, 1);
mapper->addMapping(itemText, 2, "text");
mapper->addMapping(descriptionEditor, 3);
mapper->setCurrentIndex(id);接着,我们创建映射器。QDataWidgetMapper 类允许我们通过将小部件映射到项模型的各个部分,从而创建数据感知型小部件。
addMapping() 函数在给定的控件与模型的指定部分之间建立映射关系。如果映射器的方向为水平(默认),则该部分是模型中的一列;否则,它就是一行。 我们调用 `setCurrentIndex()` 函数,使用与给定项 ID 关联的数据初始化控件。每当当前索引发生变化时,所有控件都会根据模型中的内容进行更新。
我们还将映射器的提交策略设置为“QDataWidgetMapper::ManualSubmit ”。这意味着在用户明确请求提交之前,不会将任何数据提交至数据库(另一种选择是“QDataWidgetMapper::AutoSubmit ”,即当对应控件失去焦点时自动提交更改)。 最后,我们指定了映射器视图应为其项目使用的项目委托。QSqlRelationalDelegate 类代表一种委托,它与默认委托不同,能够为作为其他表外键的字段(如我们“items”表中的“imagefile”)启用下拉列表功能。
connect(descriptionEditor, &QTextEdit::textChanged, this, [this]() { enableButtons(true); });
connect(imageFileEditor, &QComboBox::currentIndexChanged, this, [this]() { enableButtons(true); });
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(itemLabel, itemText);
formLayout->addRow(imageFileLabel, imageFileEditor);
formLayout->addRow(descriptionLabel, descriptionEditor);
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(formLayout);
layout->addWidget(buttonBox);
setLayout(layout);
itemId = id;
displayedImage = imageFileEditor->currentText();
setWindowFlags(Qt::Window);
enableButtons(false);
setWindowTitle(itemText->text());
}最后,我们将编辑器中的“内容已更改”信号连接到我们自定义的enableButtons 槽,从而允许用户提交或撤销更改。由于enableButtons 槽的签名与QTextEdit::textChanged 和QComboBox::currentIndexChanged 不匹配,因此我们需要使用lambda表达式来连接该槽。
我们将所有控件添加到布局中,存储项目 ID 和显示图像文件的名称以备后用,并设置窗口标题和初始大小。
请注意,我们还设置了Qt::Window 窗口标志,以表明我们的控件实际上是一个窗口,具有窗口系统边框和标题栏。
int InformationWindow::id() const
{
return itemId;
}窗口一旦创建,在主应用程序退出之前不会被删除(也就是说,如果用户关闭信息窗口,它只会隐藏)。 因此,我们不希望为每个项目创建多个InformationWindow 对象,并提供了公共函数id() ,以便在用户请求某项信息时,能够确定该位置是否已存在窗口。
void InformationWindow::revert()
{
mapper->revert();
enableButtons(false);
}每当用户点击Revert 按钮时,都会触发revert() 槽。
由于我们设置了QDataWidgetMapper::ManualSubmit 提交策略,除非用户明确选择提交所有更改,否则用户的任何更改都不会写回模型。不过,我们可以使用QDataWidgetMapper 的revert()槽来重置编辑器控件,将所有控件重新填充为模型的当前数据。
void InformationWindow::submit()
{
QString newImage(imageFileEditor->currentText());
if (displayedImage != newImage) {
displayedImage = newImage;
emit imageChanged(itemId, newImage);
}
mapper->submit();
mapper->setCurrentIndex(itemId);
enableButtons(false);
}同样地,当用户点击Submit 按钮提交更改时,submit() 槽会被触发。
我们使用QDataWidgetMapper 的submit() 槽方法,将映射控件中的所有更改提交至模型,即提交至数据库。对于每个映射的区域,项代理(item delegate)将从控件中读取当前值,并将其设置到模型中。 最后,调用模型的 submit()函数,告知模型应将其缓存的内容提交至永久存储。
请注意,在提交任何数据之前,我们会参考先前存储的 `displayedImage ` 变量,检查用户是否选择了另一个图像文件。如果当前文件名与存储的文件名不同,我们会存储新文件名并发出 `imageChanged() ` 信号。
void InformationWindow::createButtons()
{
closeButton = new QPushButton(tr("&Close"));
revertButton = new QPushButton(tr("&Revert"));
submitButton = new QPushButton(tr("&Submit"));
closeButton->setDefault(true);
connect(closeButton, &QPushButton::clicked, this, &InformationWindow::close);
connect(revertButton, &QPushButton::clicked, this, &InformationWindow::revert);
connect(submitButton, &QPushButton::clicked, this, &InformationWindow::submit);提供createButtons() 函数是为了方便起见,即简化构造函数。
我们将Close 按钮设为默认按钮,即用户按下Enter 时被触发的按钮,并将该按钮的clicked()信号连接到控件的close()槽。如上所述,关闭窗口只会隐藏该控件,并不会将其删除。我们还把Submit 和Revert 按钮分别连接到对应的submit() 和revert() 槽。
buttonBox = new QDialogButtonBox(this);
buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole);
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
}QDialogButtonBox 类是一个小部件,它会根据当前小部件样式,以合适的布局呈现按钮。像我们的信息窗口这样的对话框,通常会采用符合该平台界面指南的布局来呈现按钮。 不同平台的对话框布局必然各不相同。QDialogButtonBox 允许我们添加按钮,并自动采用适合用户桌面环境的布局。
对话框中的大多数按钮都遵循特定的角色。我们将“Submit ”和“Revert ”按钮设置为“reset ”角色,即表示点击该按钮会将字段重置为默认值(在本例中即数据库中包含的信息)。reject 角色表示点击该按钮将导致对话框被拒绝。另一方面,由于我们仅隐藏信息窗口,用户所做的任何更改都将被保留,直到用户明确撤销或提交这些更改为止。
void InformationWindow::enableButtons(bool enable)
{
revertButton->setEnabled(enable);
submitButton->setEnabled(enable);
}每当用户更改显示的数据时,都会调用 `enableButtons() ` 插槽来启用按钮。同样地,当用户选择提交更改时,按钮会被禁用,以表明当前数据已存储在数据库中。
至此,InformationWindow 类已开发完成。让我们来看看我们在示例应用程序中是如何使用它的。
视图类定义
View 类代表主应用程序窗口,并继承自QGraphicsView :
class View : public QGraphicsView
{
Q_OBJECT
public:
View(const QString &items, const QString &images, QWidget *parent = nullptr);
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
private Q_SLOTS:
void updateImage(int id, const QString &fileName);QGraphicsView 类是Graphics View Framework的一部分,我们将利用该框架来显示图像。为了在用户点击图像时能够响应其交互并显示相应的信息窗口,我们重写了QGraphicsView 类的mouseReleaseEvent()函数。
请注意,构造函数需要两个数据库表的名称:一个包含项目的详细信息,另一个包含可用图像文件的名称。我们还提供了一个私有updateImage() 槽,用于捕获InformationWindow 发出的imageChanged() 信号——该信号会在用户更改与项目关联的图像时触发。
private:
void addItems();
InformationWindow *findWindow(int id) const;
void showInformation(ImageItem *image);
QGraphicsScene *scene;
QList<InformationWindow *> informationWindows;addItems() 函数是一个为简化构造函数而提供的便捷函数。它仅被调用一次,用于创建各种项目并将它们添加到视图中。
另一方面,findWindow() 函数则被频繁调用。它由showInformation() 函数调用,用于判断是否已为给定项创建了窗口(每当我们创建一个InformationWindow 对象时,都会将其引用存储在informationWindows 列表中)。而后者又由我们自定义的mouseReleaseEvent() 实现调用。
QSqlRelationalTableModel *itemTable;
};最后,我们声明一个QSqlRelationalTableModel 指针。如前所述,QSqlRelationalTableModel 类提供了一个支持外键的可编辑数据模型。在使用QSqlRelationalTableModel 类时,有几点需要注意:表必须声明主键,且该主键不能包含与其他表的关联,也就是说,它不能是外键。 另请注意,如果关系表中包含引用目标表中不存在的行的键,则包含这些无效键的行将不会通过模型显示出来。维护参照完整性是用户或数据库的责任。
视图类的实现
尽管构造函数要求同时提供包含办公室详细信息的表和包含可用图像文件名称的表的名称,但我们只需为“items”表创建一个QSqlRelationalTableModel 对象:
View::View(const QString &items, const QString &images, QWidget *parent)
: QGraphicsView(parent)
{
itemTable = new QSqlRelationalTableModel(this);
itemTable->setTable(items);
itemTable->setRelation(1, QSqlRelation(images, "itemid", "file"));
itemTable->select();原因是,一旦我们拥有了包含项目详细信息的模型,就可以使用QSqlRelationalTableModel 的setRelation()函数与可用图像文件建立关联。该函数会为给定的模型列创建外键。 该外键由提供的 `QSqlRelation ` 对象指定,该对象由外键所引用的表名、外键映射到的字段以及应向用户显示的字段构造而成。
请注意,设置表仅指定模型操作的对象,即我们必须显式调用模型的select()函数来填充模型。
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 465, 365);
setScene(scene);
addItems();
setMinimumSize(470, 370);
setMaximumSize(470, 370);
QLinearGradient gradient(QPointF(0, 0), QPointF(0, 370));
gradient.setColorAt(0, QColor("#868482"));
gradient.setColorAt(1, QColor("#5d5b59"));
setBackgroundBrush(gradient);
}然后,我们创建视图的内容,即场景及其项目。标签是普通的QGraphicsTextItem 对象,而图像则是ImageItem 类的实例,该类继承自QGraphicsPixmapItem 。在回顾addItems() 函数时,我们将稍后再次讨论这一点。
最后,我们设置了主应用程序控件的大小约束和窗口标题。
void View::addItems()
{
int itemCount = itemTable->rowCount();
int imageOffset = 150;
int leftMargin = 70;
int topMargin = 40;
for (int i = 0; i < itemCount; i++) {
QSqlRecord record = itemTable->record(i);
int id = record.value("id").toInt();
QString file = record.value("file").toString();
QString item = record.value("itemtype").toString();
int columnOffset = ((i % 2) * 37);
int x = ((i % 2) * imageOffset) + leftMargin + columnOffset;
int y = ((i / 2) * imageOffset) + topMargin;
ImageItem *image = new ImageItem(id, QPixmap(":/" + file));
image->setData(0, i);
image->setPos(x, y);
scene->addItem(image);
QGraphicsTextItem *label = scene->addText(item);
label->setDefaultTextColor(QColor("#d7d6d5"));
QPointF labelOffset((120 - label->boundingRect().width()) / 2, 120.0);
label->setPos(QPointF(x, y) + labelOffset);
}
}addItems() 函数仅在创建主应用程序窗口时调用一次。对于数据库表中的每一行,我们首先使用模型的record()函数提取相应的记录。QSqlRecord 类封装了数据库记录的功能和特性,并支持添加和删除字段以及设置和检索字段值。QSqlRecord::value() 函数会将指定名称或索引的字段值作为QVariant 对象返回。
对于每一条记录,我们都会创建一个标签项和一个图像项,计算它们的位置并将其添加到场景中。图像项由ImageItem 类的实例表示。 我们必须创建自定义项类的原因在于,我们需要捕获该项的悬停事件,并在鼠标光标悬停在图像上时对该项进行动画处理(默认情况下,没有任何项会接收悬停事件)。更多详细信息请参阅《图形视图框架》文档和《图形视图示例》。
void View::mouseReleaseEvent(QMouseEvent *event)
{
if (QGraphicsItem *item = itemAt(event->position().toPoint())) {
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))
showInformation(image);
}
QGraphicsView::mouseReleaseEvent(event);
}我们重写了QGraphicsView 的mouseReleaseEvent()事件处理程序,以响应用户交互。如果用户点击任何图像项目,该函数将调用私有函数showInformation() ,以弹出相关信息窗口。
Graphics View Framework提供了qgraphicsitem_cast() 函数,用于判断给定的QGraphicsItem 实例是否属于指定类型。请注意,如果该事件与我们的任何图像项均无关,我们会将其转发给基类的实现。
void View::showInformation(ImageItem *image)
{
int id = image->id();
if (id < 0 || id >= itemTable->rowCount())
return;
InformationWindow *window = findWindow(id);
if (!window) {
window = new InformationWindow(id, itemTable, this);
connect(window, &InformationWindow::imageChanged,
this, &View::updateImage);
window->move(pos() + QPoint(20, 40));
window->show();
informationWindows.append(window);
}
if (window->isVisible()) {
window->raise();
window->activateWindow();
} else
window->show();
}showInformation() 函数接收一个ImageItem 对象作为参数,首先提取该项的项ID。
然后,它会判断该位置是否已创建了信息窗口。如果给定位置不存在窗口,我们将通过向 `InformationWindow ` 构造函数传递项目 ID、模型指针以及我们的视图(作为父视图)来创建一个窗口。 请注意,在为信息窗口设置合适的位置并将其添加到现有窗口列表之前,我们会将信息窗口的imageChanged() 信号连接到该小部件的updateImage() 槽。如果给定位置已有窗口,且该窗口处于可见状态,则会确保该窗口被提升到小部件堆栈的最顶层并被激活。 如果该窗口处于隐藏状态,调用其show()槽将产生相同的效果。
void View::updateImage(int id, const QString &fileName)
{
QList<QGraphicsItem *> items = scene->items();
while(!items.empty()) {
QGraphicsItem *item = items.takeFirst();
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) {
if (image->id() == id){
image->setPixmap(QPixmap(":/" +fileName));
image->adjust();
break;
}
}
}
}updateImage() 槽函数接受项目ID和图像文件名作为参数。它会筛选出图像项目,并使用提供的图像文件更新与给定项目ID对应的项目。
InformationWindow *View::findWindow(int id) const
{
for (auto window : informationWindows) {
if (window && (window->id() == id))
return window;
}
return nullptr;
}findWindow() 函数仅需遍历现有窗口列表,返回与给定项目ID匹配的窗口指针;若该窗口不存在,则返回nullptr 。
最后,让我们快速浏览一下我们自定义的ImageItem 类:
ImageItem 类定义
ImageItem 类的提供是为了方便图像项的动画效果。它继承自QGraphicsPixmapItem 并重写了其悬停事件处理程序:
class ImageItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
enum { Type = UserType + 1 };
ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
void adjust();
int id() const;
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private Q_SLOTS:
void setFrame(int frame);
void updateItemPosition();
private:
QTimeLine timeLine;
int recordId;
double z;
};我们为自定义项声明了一个Type 枚举值,并重写了type()方法。这样做是为了能够安全地使用qgraphicsitem_cast()方法。此外,我们实现了一个公共函数id() ,用于识别关联的位置;以及一个公共函数adjust() ,调用该函数可确保图像项获得首选尺寸,而与原始图像文件无关。
动画效果通过QTimeLine 类、事件处理程序以及私有插槽setFrame() 实现:当鼠标光标悬停在图像项上时,该图像项会展开;当光标移出其边界时,则恢复到原始大小。
最后,我们存储了该特定记录关联的项 ID 以及 z 值。在Graphics View Framework 中,项的 z 值决定了其在项堆栈中的位置。如果两个项共享同一个父项,则 z 值较高的项将绘制在 z 值较低的项之上。 我们还提供了一个updateItemPosition() 函数,用于在需要时刷新视图。
ImageItem 类的实现
ImageItem 类实际上只是QGraphicsPixmapItem 类的扩展,仅增加了若干功能,即我们可以将构造函数的大部分参数(位图、父项和场景)传递给基类的构造函数:
ImageItem::ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent)
: QGraphicsPixmapItem(pixmap, parent)
{
recordId = id;
setAcceptHoverEvents(true);
timeLine.setDuration(150);
timeLine.setFrameRange(0, 150);
connect(&timeLine, &QTimeLine::frameChanged, this, &ImageItem::setFrame);
connect(&timeLine, &QTimeLine::finished, this, &ImageItem::updateItemPosition);
adjust();
}然后,我们将 ID 存储起来以备后用,并确保我们的图像项能够接收悬停事件。当当前没有鼠标抓取项时,系统会触发悬停事件。这些事件会在鼠标光标进入某项、在项内移动以及离开该项时发送。 正如我们之前提到的,Graphics View Framework 中的所有项默认都不接受悬停事件。
QTimeLine 类提供了一个用于控制动画的时间轴。其duration 属性存储了时间轴的总持续时间(以毫秒为单位)。默认情况下,时间轴会从开始处向结束处运行一次。QTimeLine::setFrameRange()函数用于设置时间轴的帧计数器;当时间轴运行时,每次帧发生变化,都会发出frameChanged()信号。 我们为动画设置了持续时间和帧范围,并将时间线的frameChanged()和finished()信号连接到我们的私有插槽setFrame() 和updateItemPosition() 上。
最后,我们调用adjust() ,以确保该项目被赋予首选大小。
void ImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Forward);
if (z != 1.0) {
z = 1.0;
updateItemPosition();
}
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}
void ImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Backward);
if (z != 0.0)
z = 0.0;
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}每当鼠标光标进入或离开图像项时,相应的事件处理程序就会被触发:我们首先设置时间轴的方向,使项分别展开或收缩。然后,如果项的 z 值尚未设置为预期值,我们就对其进行调整。
对于悬停进入事件,我们会立即更新项的位置,因为我们希望该项在开始扩展时立即显示在所有其他项之上。另一方面,对于悬停离开事件,我们会推迟实际更新以实现相同的效果。 但请记住,在创建该项时,我们已将时间线的finished()信号连接到了updateItemPosition() 槽。这样,动画完成后,该项便会在项堆栈中获得正确的位置。最后,如果时间线尚未运行,我们会启动它。
void ImageItem::setFrame(int frame)
{
adjust();
QPointF center = boundingRect().center();
setTransform(QTransform::fromTranslate(center.x(), center.y()), true);
setTransform(QTransform::fromScale(1 + frame / 300.0, 1 + frame / 300.0), true);
setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true);
}当时间线正在运行时,由于我们在项构造函数中建立的连接,每当当前帧发生变化,它都会触发setFrame() 槽。正是这个槽控制着动画,使图像项逐步扩大或缩小。
我们首先调用adjust() 函数,以确保从项的原始大小开始。然后根据动画的进度(使用frame 参数),按相应比例对项进行缩放。 请注意,默认情况下,变换是以项的左上角为基准的。由于我们希望项相对于其中心进行变换,因此在缩放项之前,必须先平移坐标系。
最终,只剩下以下几个便捷函数:
void ImageItem::adjust()
{
setTransform(QTransform::fromScale(120.0 / boundingRect().width(),
120.0 / boundingRect().height()));
}
int ImageItem::id() const
{
return recordId;
}
void ImageItem::updateItemPosition()
{
setZValue(z);
}adjust() 函数定义并应用了一个变换矩阵,确保无论源图像大小如何,我们的图像项都能以首选尺寸显示。id() 函数非常简单,仅用于识别该项。在updateItemPosition() 槽中,我们调用QGraphicsItem::setZValue()函数,设置该项的浮动高度。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.