QTableView の列固定 (スクロール固定)
QT の QTableView で業務システム等でよく要望される、左からN列目までは横スクロールしないように、固定化する方法です。
はじめに
標準の QTableView には、スクロール固定を行う機能がないため、カスタムウェジットを作成します。
公式でも以下のページで固定化を実現方法が説明されています。
Frozen Column Exampleこのやり方でも、スクロール固定での表示自体はできますが、
http://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html
固定列側のセルをクリックしても clicked シグナルが発生しないなど、
表示だけでなく、QTableView を操作・編集するようなアプリの場合に
色々機能が足りません。
今回は、それを改善した物を作ろうと思います。
実現方法
列固定は以下のイメージのように、メインの QTableVIew の上に、スクロール固定列を表示する、もう一つの QTableView を重ねて配置し、
1つの Tableに見せかけます。
ソースコード
freezetableview.h#ifndef FREEZETABLEVIEW_H
#define FREEZETABLEVIEW_H
#include <QtWidgets>
#include <QtGui>
class FreezeTableView : public QTableView
{
Q_OBJECT
public:
explicit FreezeTableView(QWidget *parent = 0);
~FreezeTableView();
Q_PROPERTY(int freezeColumnCount READ freezeColumnCount WRITE setFreezeColumnCount NOTIFY freezeColumnCountChanged)
void initFreezeColumn();
int freezeColumnCount() const { return m_freezeColumnCount; }
void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate);
void edit(const QModelIndex &index);
signals:
void freezeColumnCountChanged(int freezeColumnCount);
protected slots:
void columnResized(int column, int, int newWidth);
protected:
void resizeEvent(QResizeEvent *event) override;
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
void scrollTo (const QModelIndex & index, ScrollHint hint = EnsureVisible) override;
public slots:
void setFreezeColumnCount(int freezeColumnCount) {
if (m_freezeColumnCount == freezeColumnCount)
return;
m_freezeColumnCount = freezeColumnCount;
emit freezeColumnCountChanged(freezeColumnCount);
}
void hideColumn(int column);
void showColumn(int column);
void sortByColumn(int column);
void sortByColumn(int column, Qt::SortOrder order);
private:
QTableView *frozenTableView;
int m_freezeColumnCount;
bool freezenTableColumnResizing_;
bool tableColumnResizing_;
void updateFrozenTableGeometry();
int frozenColumnWidth();
private slots:
void updateSectionWidth(int logicalIndex, int oldSize, int newSize);
void updateSectionHeight(int logicalIndex, int oldSize, int newSize);
void updateFrozenTableSectionWidth(int logicalIndex, int, int newSize);
};
#endif // FREEZETABLEVIEW_H
freezetableview.cpp#include <QtGlobal>
#ifdef Q_OS_WIN
#pragma execution_character_set("utf-8")
#endif
#include <QScrollBar>
#include <QHeaderView>
#include "freezetableview.h"
FreezeTableView::FreezeTableView(QWidget *parent) :
QTableView(parent)
{
connect(this->horizontalHeader(),SIGNAL(sectionResized(int,int,int)),
this,SLOT(columnResized(int,int,int)));
// 固定列表示用のTableViewを初期化する
frozenTableView = new QTableView(this);
// 固定列数の変数初期化
m_freezeColumnCount = 0;
freezenTableColumnResizing_ = false;
tableColumnResizing_ = false;
}
FreezeTableView::~FreezeTableView()
{
delete frozenTableView;
}
void FreezeTableView::columnResized(int column, int /*oldWidth*/, int newWidth)
{
if (newWidth > this->width() / 2)
{
this->setColumnWidth(column, this->width() / 2);
}
}
void FreezeTableView::initFreezeColumn() {
/// 固定列表示用の QTableVIew にプロパティをコピー
frozenTableView->setModel(model());
frozenTableView->setFocusPolicy(Qt::NoFocus);
frozenTableView->verticalHeader()->hide();
frozenTableView->setSelectionBehavior(selectionBehavior());
frozenTableView->setSelectionMode(selectionMode());
frozenTableView->horizontalHeader()->setMinimumHeight(this->horizontalHeader()->minimumHeight());
frozenTableView->setEditTriggers(this->editTriggers());
viewport()->stackUnder(frozenTableView);
frozenTableView->setSelectionModel(selectionModel());
frozenTableView->setContextMenuPolicy(contextMenuPolicy());
/// 固定列表示用の QTableView は固定列のみを画面に表示し、それ以外を非表示にする
for (int col = freezeColumnCount(); col < model()->columnCount(); ++col)
frozenTableView->setColumnHidden(col, true);
/// 列幅同期
for (int col = 0; col < freezeColumnCount(); ++col)
frozenTableView->setColumnWidth(col, columnWidth(col));
/// 固定列表示用の QTableView はスクロールバーを表示しない
frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
frozenTableView->show();
/// 固定列表示用の QTableView の表示位置設定
updateFrozenTableGeometry();
/// スクロールの設定を行う。
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
frozenTableView->setVerticalScrollMode(ScrollPerPixel);
const QColor hlClr = Qt::red; // highlight color to set
const QColor txtClr = Qt::white; // highlighted text color to set
QPalette p = palette();
p.setColor(QPalette::Highlight, hlClr);
p.setColor(QPalette::HighlightedText, txtClr);
/// シグナル・スロット接続
/// - ヘッダのリサイズ(幅)
connect(horizontalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionWidth);
/// - ヘッダのリサイズ(幅) 固定列表示のテーブル
connect(frozenTableView->horizontalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableView::updateFrozenTableSectionWidth);
/// - ヘッダのリサイズ(高さ)
connect(verticalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionHeight);
/// - 固定列表示用の QTableView 縦スクロール
connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged,
verticalScrollBar(), &QAbstractSlider::setValue);
/// - スクロール列表示用の QTableView 縦スクロール
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);
/// - clicked シグナルの連動
QObject::connect(frozenTableView, &QTableView::clicked, [&](const QModelIndex &index){
emit clicked(index);
});
/// - doubleClicked シグナルの連動
QObject::connect(frozenTableView, &QTableView::doubleClicked, [&](const QModelIndex &index){
emit doubleClicked(index);
});
/// sectionClicked シグナルの連動
QObject::connect(frozenTableView->horizontalHeader(), &QHeaderView::sectionClicked, [&](int logicalIndex){
emit horizontalHeader()->sectionClicked(logicalIndex);
});
/// - doubleClicked シグナルの連動
QObject::connect(frozenTableView, &QTableView::customContextMenuRequested, [&](const QPoint &pos){
emit customContextMenuRequested(pos);
});
}
void FreezeTableView::setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate)
{
QTableView::setItemDelegateForColumn(column, delegate);
if (column < freezeColumnCount()) {
frozenTableView->setItemDelegateForColumn(column, delegate);
}
}
void FreezeTableView::edit(const QModelIndex &index)
{
if (index.column() < freezeColumnCount()) {
frozenTableView->edit(index);
} else {
QTableView::edit(index);
}
updateFrozenTableGeometry();
}
void FreezeTableView::hideColumn(int column) {
QTableView::hideColumn(column);
frozenTableView->hideColumn(column);
if (freezeColumnCount() > 0) {
/// 固定列表示用の QTableView は固定列のみを画面に表示し、それ以外を非表示にする
for (int col = freezeColumnCount(); col < model()->columnCount(); ++col) {
tableColumnResizing_ = true;
frozenTableView->setColumnHidden(col, true);
tableColumnResizing_ = false;
}
updateFrozenTableGeometry();
}
}
void FreezeTableView::showColumn(int column) {
QTableView::showColumn(column);
if (column < freezeColumnCount()) {
bool isHidden = frozenTableView->isColumnHidden(column);
frozenTableView->showColumn(column);
if (isHidden) {
updateFrozenTableGeometry();
}
}
}
void FreezeTableView::sortByColumn(int column)
{
if (column < freezeColumnCount()) {
frozenTableView->sortByColumn(column);
} else {
QTableView::sortByColumn(column);
}
}
void FreezeTableView::sortByColumn(int column, Qt::SortOrder order)
{
if (column < freezeColumnCount()) {
frozenTableView->sortByColumn(column, order);
} else {
QTableView::sortByColumn(column, order);
}
}
void FreezeTableView::updateSectionWidth(int logicalIndex, int /* oldSize */, int newSize) {
if (freezenTableColumnResizing_) {
return;
}
if (logicalIndex <= freezeColumnCount() - 1){
tableColumnResizing_ = true;
frozenTableView->setColumnWidth(logicalIndex, newSize);
tableColumnResizing_ = false;
updateFrozenTableGeometry();
}
}
void FreezeTableView::updateFrozenTableSectionWidth(int logicalIndex, int /* oldSize */, int newSize) {
if (tableColumnResizing_) {
return;
}
freezenTableColumnResizing_ = true;
setColumnWidth(logicalIndex, newSize);
freezenTableColumnResizing_ = false;
updateFrozenTableGeometry();
}
void FreezeTableView::updateSectionHeight(int logicalIndex, int /* oldSize */, int newSize) {
frozenTableView->setRowHeight(logicalIndex, newSize);
}
void FreezeTableView::resizeEvent(QResizeEvent * event) {
/// QTableView のサイズが変われば、固定列表示用の QTableView にも反映する
QTableView::resizeEvent(event);
updateFrozenTableGeometry();
}
QModelIndex FreezeTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) {
QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);
int width = frozenColumnWidth();
if (cursorAction == MoveLeft && current.column() > (freezeColumnCount() - 1)
&& visualRect(current).topLeft().x() < width ) {
const int newValue = horizontalScrollBar()->value() + visualRect(current).topLeft().x() - width;
horizontalScrollBar()->setValue(newValue);
}
return current;
}
void FreezeTableView::scrollTo (const QModelIndex & index, ScrollHint hint) {
if (index.column() > freezeColumnCount() - 1)
QTableView::scrollTo(index, hint);
}
void FreezeTableView::updateFrozenTableGeometry() {
//モデルの初期化が未 or 列数が0の場合は処理スキップ
if (model() == NULL || model()->columnCount() == 0) {
frozenTableView->setGeometry(0, 0, 0, 0);
return;
}
//固定する列数が未設定の場合、処理スキップ
if (freezeColumnCount() == 0) {
frozenTableView->setGeometry(0, 0, 0, 0);
return;
}
int width = frozenColumnWidth();
//固定表示用の TableWidget のサイズ調整
frozenTableView->setGeometry(verticalHeader()->width(),
0 , width ,
viewport()->height()+horizontalHeader()->height());
}
int FreezeTableView::frozenColumnWidth() {
int width = 0;
for (int i = 0; i < freezeColumnCount(); i++) {
if (!isColumnHidden(i)) {
width += columnWidth(i);
}
}
return width;
}
使用方法
- freezetableview.h、freezetableview.cpp の2つを自分のプロジェクトに取り込んでください。
- デザイナを開いてスクロール固定したい QTableVIew を選択します。
- 右クリックメニューより [格上げ先を指定]を選択
- 以下のイメージの通り、格上げ先を指定します
- 初期化用のコードを記述します
// 2列目までを固定する設定で、カスタムウェジットを初期化
ui->tableView->initFreezeColumn(2);
0 件のコメント:
コメントを投稿