Automating Excel in Qt with QAxObject for Template-Based Writes and Batch Updates
QAxObject enables COM automation of Microsoft Excel on Windows. When an .xlsx file is opened by Excel, a hidden lock file named ~$.xlsx is created in the same directoyr. If this lock file is present, writing via QAxObject typically fails due to the workbook being read-only. Detecting and avoiding this condition is required before automation.
A template workbook placed alongside the executable can be copied per run, then populated through QAxObject calls. The snippet below demonstrates copying a template, checking for the lock file, writing individual cells, and performing a fast batch update over a contiguous range.
#include <QApplication>
#include <QAxObject>
#include <QFileDialog>
#include <QStandardPaths>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QColor>
#include <QDebug>
// Excel columns mapping (1-based)
enum XlCol {
ColA = 1,
ColB,
ColC,
ColD,
ColE,
ColF,
ColG,
ColH,
ColI
};
// Prompt for a destination .xlsx path
QString promptSavePath()
{
QString selectedFilter;
const QString desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
return QFileDialog::getSaveFileName(
nullptr,
QStringLiteral("Save As"),
desktop,
QStringLiteral("Excel (*.xlsx)"),
&selectedFilter);
}
// Write a single cell: value + text color
void setCell(QAxObject* sheet, XlCol column, int row, const QColor& textColor, const QString& value)
{
QAxObject* cell = sheet->querySubObject("Cells(int,int)", row, column);
cell->setProperty("Value", value);
QAxObject* font = cell->querySubObject("Font");
font->setProperty("Color", textColor);
}
// Convert QVariant (2D SAFEARRAY) -> QList<QList<QVariant>> for fast reads
void to2DVariantList(const QVariant& src, QList<QList<QVariant>>& out)
{
const QVariantList rows = src.toList();
for (const QVariant& r : rows) {
out.push_back(r.toList());
}
}
// Convert QList<QList<QVariant>> -> QVariant for fast writes
void from2DVariantList(const QList<QList<QVariant>>& cells, QVariant& out)
{
QVariantList rows;
rows.reserve(cells.size());
for (const QList<QVariant>& r : cells) {
rows.append(QVariant(r));
}
out = QVariant(rows);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 1) Resolve template path and ensure existence
QString templatePath = QStringLiteral("./template.xlsx");
QFileInfo tplInfo(templatePath);
if (!tplInfo.exists()) {
qDebug() << "Missing template.xlsx";
return 0;
}
templatePath = QDir::toNativeSeparators(tplInfo.absoluteFilePath());
// 2) Choose destination and copy template
const QString targetXlsx = QDir::toNativeSeparators(promptSavePath());
if (targetXlsx.isEmpty()) {
return 0;
}
if (!QFile::copy(templatePath, targetXlsx)) {
qDebug() << "Failed to copy template to" << targetXlsx;
return 0;
}
// 3) Detect Excel lock (~$...) indicating read-only state
QFileInfo targetInfo(targetXlsx);
const QString lockName = QStringLiteral("~$") + targetInfo.fileName();
const QString lockPath = targetInfo.dir().absoluteFilePath(lockName);
if (QFileInfo(lockPath).exists()) {
qDebug() << "Workbook appears locked by Excel (read-only). Close the file and retry.";
return 0;
}
// 4) Launch Excel via COM and open workbook
QAxObject excel("Excel.Application");
excel.setProperty("Visible", true);
excel.setProperty("DisplayAlerts", false);
QAxObject* workbooks = excel.querySubObject("Workbooks");
QAxObject* workbook = workbooks->querySubObject("Open(const QString&)", QDir::toNativeSeparators(targetXlsx));
excel.setProperty("Caption", "Qt Excel Automation");
// Pick first worksheet
QAxObject* sheet = workbook->querySubObject("Worksheets(int)", 1);
// 5) Individual cell updates
setCell(sheet, ColB, 2, QColor(0, 128, 255), QStringLiteral("12345"));
setCell(sheet, ColB, 3, QColor(255, 165, 0), QStringLiteral("B3"));
setCell(sheet, ColB, 4, QColor(200, 0, 0), QStringLiteral("B4"));
// 6) Batch write to contiguous range
// Range size: 100 rows x 9 cols (A6:I105)
QAxObject* range = sheet->querySubObject("Range(const QString&)", "A6:I105");
QList<QList<QVariant>> matrix;
matrix.reserve(100);
for (int r = 0; r < 100; ++r) {
QList<QVariant> row;
row.reserve(9);
for (int c = 0; c < 9; ++c) {
row.append(r + c); // example data
}
matrix.append(row);
}
QVariant safeArray;
from2DVariantList(matrix, safeArray);
range->setProperty("Value", safeArray);
// 7) Save; optionally close workbook and quit Excel
workbook->dynamicCall("Save()");
// workbook->dynamicCall("Close()");
// excel.dynamicCall("Quit()");
return 0;
}