はじめに
前回、Python で PyQt6 を使っていたときにハマった話を紹介したが、
ちょっと気になったので C++ で直に Qt6 のライブラリを使った場合を確認してみたという話。
先に結論を書いておくが、結果は同じ。(考えてみれば当たり前なんだろうけど)
- ResizeToContents を設定している QHeaderView を使った QTableView で、アイテム数の少ないモデルに切り替えた際、Out of range を起こす。
したがって、新たな情報はあまり無いが、メモを残しておく。
確認環境
OS: Ubuntu 22.04.2 (WSL2: Windows 11 Home (22H2))
Qt: 6.5.2 (自分でインストール)
確認用コード
例によって動かすことを主眼に、シンプルに書いているので、かなり簡素化したコードになっている。少なくともコーディングスタイルのお手本にはけっしてならない類のモノだとは、先にお断りしておく。
// -*- coding: utf-8 -*-
#ifndef MAIN_H
#define MAIN_H
#include <vector>
#include <string>
#include <QMainWindow>
#include <QMenuBar>
#include <QTableView>
#include <QHeaderView>
#include <QAbstractTableModel>
class _Model: public QAbstractTableModel
{
Q_OBJECT
public:
using items_type = std::vector<std::vector<std::string> >;
using headers_type = std::vector<std::string>;
_Model(items_type const &items, headers_type const &headers)
: _items(items), _headers(headers) {}
~_Model() override = default;
int rowCount(const QModelIndex &parent = QModelIndex()) const {
return _items.size(); }
int columnCount(const QModelIndex &parent = QModelIndex()) const {
return _headers.size(); }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
private:
items_type _items;
headers_type _headers;
};
class _HHeader: public QHeaderView
{
Q_OBJECT
public:
_HHeader(QWidget *parent = nullptr)
: QHeaderView(Qt::Horizontal, parent) {
setSectionResizeMode(ResizeMode::ResizeToContents);
}
~_HHeader() override = default;
};
class _Table: public QTableView
{
Q_OBJECT
public:
_Table(QWidget *parent = nullptr)
: QTableView(parent) {
setHorizontalHeader(new _HHeader(this));
}
~_Table() override = default;
};
class Main: public QMainWindow
{
Q_OBJECT
public:
Main();
~Main() override = default;
public slots:
void setModel1();
void setModel2a();
void setModel2b();
private:
void _setupUI();
QTableView *tv;
QAbstractTableModel *m1, *m2;
};
#endif
// -*- coding: utf-8 -*-
#include <iostream>
#include <QApplication>
#include "main.h"
QVariant _Model::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
auto nrow = rowCount();
auto ncol = columnCount();
if (index.row () >= nrow || index.column() >= ncol) {
std::cout << "Model::data(" << index.row() << "," << index.column()
<< "), role: " << role << std::endl;
return QVariant();
}
return QString(_items[index.row()][index.column()].c_str());
}
return QVariant();
}
QVariant _Model::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal &&
role == Qt::DisplayRole) {
auto ncol = columnCount();
if (section >= ncol) {
std::cout << "Model::section: " << section
<< ", columnCont: " << ncol
<< ", orientation: " << orientation
<< ", role: " << role << std::endl;
return QVariant();
}
return QString(_headers[section].c_str());
}
return QAbstractTableModel::headerData(section, orientation, role);
}
Main::Main()
{
_setupUI();
m1 = new _Model({{"a1", "b1", "c1"}, {"a2", "b2", "c2"}},
{"#1", "#2", "#3"});
m2 = new _Model({}, {});
}
void Main::_setupUI()
{
setWindowTitle("test");
resize(600, 400);
auto filemenu = menuBar()->addMenu("テスト(&T)");
connect(filemenu->addAction("Model #1"), &QAction::triggered,
this, &Main::setModel1);
connect(filemenu->addAction("Model #2a"), &QAction::triggered,
this, &Main::setModel2a);
connect(filemenu->addAction("Model #2b"), &QAction::triggered,
this, &Main::setModel2b);
{
auto act = filemenu->addAction("閉じる(&X) ");
act->setShortcut(tr("Ctrl+Q"));
act->setStatusTip("ウィンドウを閉じる");
connect(act, &QAction::triggered, this, &Main::close);
}
tv = new _Table(this);
setCentralWidget(tv);
}
void Main::setModel1()
{
std::cout << "setModel1" << std::endl;
tv->setModel(m1);
}
void Main::setModel2a()
{
std::cout << "setModel2a" << std::endl;
tv->setModel(m2);
}
void Main::setModel2b()
{
std::cout << "setModel2b" << std::endl;
tv->setModel(nullptr);
tv->setModel(m2);
}
int main(int argv, char *args[])
{
QApplication app(argv, args);
Main w;
w.show();
return app.exec();
}
cmake_minimum_required(VERSION 3.16)
project(Qt6TableTest1 VERSION 0.0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
add_executable(a.out main.cxx)
target_link_libraries(a.out PRIVATE Qt6::Widgets)
動作確認
1. コンパイルから起動までは以下のように実行すればできる(ハズ)。
$ cmake -S . -B build
$ cmake --build build/
$ ./build/a.out
2. ウィンドウが立ち上がったら、メニューの [テスト] から [Model #1] を選ぶ。
⇒縦2,横3のテーブルが表示される。
3. メニューの [テスト] から [Model #2a]を選ぶ。
⇒エラー発生。以下のようなメッセージが表示されるハズ。
$ ./build/a.out
setModel1
setModel2a
Model::section: 0, columnCont: 0, orientation: 1, role: 0
Model::section: 1, columnCont: 0, orientation: 1, role: 0
Model::section: 2, columnCont: 0, orientation: 1, role: 0
(あ、スペルミスがある…… orz)
終わりに
回避策としては、いったん nullptr を setModel に入れれば良いことはわかっている。
なので、これ以上追求するモチベーションはあまり無いのだけれど、もしかしたら Qt のコードを追ってデバッグしてみるのも一興かも?