スキップしてメイン コンテンツに移動
QTableView の列固定 (スクロール固定)

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に見せかけます。
enter image description here

ソースコード

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;
}

使用方法

  1. freezetableview.h、freezetableview.cpp の2つを自分のプロジェクトに取り込んでください。
  2. デザイナを開いてスクロール固定したい QTableVIew を選択します。
  3. 右クリックメニューより [格上げ先を指定]を選択
  4. 以下のイメージの通り、格上げ先を指定します
enter image description here
enter image description here
  1. 初期化用のコードを記述します
// 2列目までを固定する設定で、カスタムウェジットを初期化
ui->tableView->initFreezeColumn(2);
スポンサーリンク
スポンサーリンク
スポンサーリンク

コメント

このブログの人気の投稿

axiosの使い方まとめ (GET/POST/例外処理)

axiosの使い方まとめ (GET/POST/例外処理)最近何かとよく使うJavaScriptでAJAX通信を行うaxiosについて、簡単に使い方をまとめました。GETリクエストをaxiosで送るまずはGETリクエストをaxiosで送る方法です。const res =await axios.get('/users') console.log(res.data)分割代入の記法を使うと、以下のようにも書けますconst{data}=await axios.get('/users') console.log(data)クエリパラメータ (URLパラメータ)を指定クエリパラメータを指定する方法は2つあります。1つ目は、axios.getに指定するURLに直接記述する方法です。axios.get('/user?id=123')2つめは、axios.getの第2引数に、オプション指定する方法です。axios.get('/user',{ params:{ id:123}})POSTリクエストをaxiosで送る次はPOSTリクエストをaxiosで送る方法です。JSON形式でPOSTするJSON形式でPOSTする場合は、axios.postの第2引数に、送信するデータをJavaScriptオブジェクトで指定します。const res =await axios.post('/user',{ id:123, name:'Yamada Tarou'})application/x-www-form-urlencoded形式でPOSTするapplication/x-www-form-urlencoded形式でPOSTする場合は、URLSearchParamsを使います。var params =newURLSearchParams() params.append('id',123) params.append('name','Yamada Tarou')const res =await axios.post('/user', params)axios でファイルをアップロードする画像などのファイルを、axiosでアッ…

[VB, C#] Windows 8, Window 10 で ImeModeが制御できない問題を解決する

[VB, C#] Windows 8, Window 10 で ImeModeが制御できない問題を解決するタイトルの通りですが、Windows 8 以降では Windows Form アプリケーションで、コントロールの ImeMode に Katakana や KatakanaHalf を設定しても、カタカナになってくれません。なぜ ImeMode が効かないのか?Windows 8 以降、IME Mode の切り替えは、ユーザー単位で切り替わるようになった為、アプリから IME Mode 制御が出来ないようになりました。
(IME をON にした場合、常に ひらがな モードになます)※ Windows 7までは、IME Modeの切り替えはアプリ単位で行われていた為、問題なくアプリから IME制御が行えました。対処方法Windows 8 以降、IMEの制御は、InputScope クラスの利用が推奨されています。
しかし、InputScope クラスは、WPF、Windows ストアアプリでしか使えない為、Windows Formアプリでは使用できません。
(Windows Form はもう使うな!という事でしょうか (涙) )結論としては、コントールパネルの設定で、IMEの制御をユーザ単位から アプリ単位に変更する事ができます。
これで、Windows Formアプリでも 従来通りIMEの制御を行う事が出来ます。おわりにこの方法だと、アプリをインストールする端末すべてに設定が必要となり、とっても面倒です。。。
しかし、今の所これしか方法がない状態です。
これからは Windows Formではなく、WPFや Windows ストアアプリで作れという事ですかね (^^;)

MailKitの使い方! エンコーディング指定や添付ファイをメールで送信する方法[C#/VB Tips]

MailKitの使い方! エンコーディング指定や添付ファイをメールで送信する方法[C#/VB Tips]MailKitを使ってメールを送るサンプルコードです。(C#)UTF8/iso-2022-jpのエンコーディング指定、GMail/YahooのSMTPサーバで送るなど、4つのサンプルコードでMailKitの使い方を紹介します。MailKitって何?2017年に.NET標準のSystem.Net.Mail.SmtpClientが廃止予定となり、Microsoftより今後はオープンソースライブラリである、MailKitに置き換えるとアナウンスがありました。既にSmtpClientは非推奨になっており、今後は廃止されていきます。現在、SmtpClientを使用したソースコードには、Visual StudioからMailKitを使うよう警告が出るようになっています。さっそく、MailKitを使ってメールを送信するサンプルコードを作っていきます。UTF8でメールを送信文字エンコーディングを、UTF8でメールを送信するサンプルコードです。
MailKitは、デフォルトの文字エンコーディングがUTF8なっている為、シンプルなコードでメールを送信する事ができます。var host ="<smtp server name>"; var port =25;// or 587using(var smtp =new MailKit.Net.Smtp.SmtpClient()){//SMTPサーバに接続する smtp.Connect(host, port, MailKit.Security.SecureSocketOptions.Auto);//認証が必要な場合は、以下のコメントを解除//smtp.Authenticate("<id>", "<password>");//送信するメールを作成する var mail =new MimeKit.MimeMessage(); var builder =new MimeKit.BodyBuilder(); mail.From.Add(new MimeKit.MailboxAddress("",&quo…