12

I'm using a QTabWidget and I need a way to handle the change of the current tab before it actually happens, and possibly cancel it if certain conditions are met. The QTabWidget::currentChanged signal is received after the current tab has changed, but is there a QTabWidget::currentChanging signal or another way to achieve the behavior I need?

Daniel Hedberg
  • 5,677
  • 4
  • 36
  • 61
user360607
  • 363
  • 1
  • 5
  • 14
  • 4
    I don't believe there is such a hook...you would have to manually flip back. But this doesn't sound like a very good user interface choice. Why wouldn't you make the tab's enablement state reflect its availability instead of trying to "reject" a click on a valid-looking tab? http://doc.qt.nokia.com/latest/qtabwidget.html#setTabEnabled – HostileFork says dont trust SE Nov 29 '11 at 09:49
  • Would it be possible to subclass QTabWidget? I haven't done any research on the matter; it's just an idea. – Chris Parton Nov 29 '11 at 09:55
  • We had exactly this problem and eventually chose to write a custom TabWidget from scratch which produced an about-to-change signal and allowed objects to veto proposed changes. Having said that I would have chosen to use the method proposed by @HostileFork if that had been an option. – sam-w Nov 29 '11 at 09:56
  • @sjwarner Any solution that makes the user feel like they're in control is better than one where it feels like it is broken or failing to act like they would expect. Another choice would be that instead of flipping back and "undoing" their seemingly-legitimate tab click, one could use something like a `QStackedWidget` on the target tab. It could by default have a widget in it that said "nothing to see here unless you go back to some other tabs". But if the checks passed it would show the "real" tab page contents. http://doc.qt.nokia.com/latest/qstackedwidget.html#details – HostileFork says dont trust SE Nov 29 '11 at 11:04
  • I have successfully achieved such a behaviour by inheriting QTabWidget. As I do not have access to the code now, I'll wait until I go home to post my code if it's of any interest. – ixM Nov 29 '11 at 12:17
  • 1
    Just as a simple idea why not disable tabs from changing if the condition is met? Don't let the user change tabs at all. – Karlson Mar 29 '12 at 18:58
  • @ixM Could you answer with the solution? thanks! – Javi Carnero Feb 18 '13 at 13:23

5 Answers5

11

In my case, I connect SIGNAL and SLOT like this:

//check if user clicked at a tab
connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSelected()));

and in tabSelected() function, I check current Tab Index:

void MainWindow::tabSelected(){
    if(ui->tabWidget->currentIndex()==0){

        // Do something here when user clicked at tab1

    }
    if(ui->tabWidget->currentIndex()==3){

        // Do something here when user clicked at tab4

    }
}
thangdc94
  • 1,542
  • 2
  • 26
  • 38
4

This is how I solved it

void MainWindow::on_tabWidget_currentChanged(int index)
{
    if (lockTabs) ui->tabWidget->setCurrentIndex(lockedTab);
}

On click of a button, I set lockTabs to true and save the current tab index to lockedTab (int). No matter what tab you click it will just throw you back to the locked tab.

I do agree with the first comment that disabling tabs is the better way tho. This is my solution for disabling tabs:

void MainWindow::lockTabs(int except){
    for (int i=0; i<ui->tabWidget->count(); i++) {
        if (i!=except) ui->tabWidget->setTabEnabled(i, false);
    }
}

void MainWindow::unlockTabs() {
    for (int i=0; i<ui->tabWidget->count(); i++) {
        ui->tabWidget->setTabEnabled(i, true);
    }
}
cen
  • 2,873
  • 3
  • 31
  • 56
1

In your header, declare:

QWidget *saveTab

Create a routine tabChanged have the slot for the currentChanged() signal. Then:

void pkgName::tabChanged
//"ask your question"
if "bad reply"
  // This is where you'll "set back to your old tab"
  ui->tabWidget->setCurrentWidget(savedWidget)
end if
savedWidget = ui->tabWidget-> getCurrentWidget() 
// Process
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
WillW
  • 81
  • 2
1

There is a simple solution with event filters that doesn't require to subclass QTabWidget. In my case I needed to disable switching to a particular tab

ui->tabWidget->tabBar()->installEventFilter(this);

then:

bool MainWindow::eventFilter(QObject* watched, QEvent* event)
{
    if(watched == ui->tabWidget->tabBar())
    {
        if(event->type() == QEvent::MouseButtonPress)// || event->type() == QEvent::MouseButtonRelease)
        {
            auto pos = dynamic_cast<QMouseEvent*>(event)->pos();
            auto index = ui->tabWidget->tabBar()->tabAt(pos);
            if(ui->tabWidget->widget(index) == ui->addButtonTab)
                return true; // cancell event
        }
    }
    return QMainWindow::eventFilter(watched, event);
}

At the stage of mouse click it is possible to retrieve index of a currently selected tab and prepare it for being switched(or cancel switching as done in my example).

andrey.s
  • 789
  • 10
  • 28
0

Using a regular QTabWidget and flipping back to previous tab after currentChanged was emitted if change was forbidden does not look right for user as the new tab is made visible before the previous one is re-selected, this is due to QTabWidget informing the tab "changed", not "is about to change".

One option is to create your own QTabWidget. Thanks to QTabBar, this is pretty obvious.

You'll also need to create QTabWidget like function to use it "like" a QTabWidget, but there's not so many function you'll need.

Here is an example of QTabWidget like class with a aboutToChangeTab signal being emitted informing that tab is about to be changed, one may set allowed to false to forbid tab change.

#pragma once

#include <QWidget>

class QTabBar;
class QStackedWidget;

class SmartTabWidget : public QWidget
{
    Q_OBJECT

    typedef QWidget baseClass;

public:
    SmartTabWidget( QWidget* parent );

    int addTab(QWidget* page, const QString& label);
    int addTab(QWidget* page, const QIcon& icon, const QString& label);

    int currentIndex() const;
    QWidget* widget( int index );

signals:
    void aboutToChangeTab( QWidget* curTab, QWidget* nextTab, bool* allowed );

private slots:
    void tryToChangeTab( int index );

private:
    QTabBar* m_tab;
    QStackedWidget* m_stack;
};

And:

#include "smart_tab_widget.h"

#include <QTabBar>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QIcon>

SmartTabWidget::SmartTabWidget( QWidget* widget ) :
    baseClass( widget )
{
    new QVBoxLayout( this );
    layout()->setContentsMargins( 0,0,0,0 );

    layout()->addWidget( m_tab = new QTabBar(this) );
    layout()->addWidget( m_stack = new QStackedWidget(this) );

    connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(tryToChangeTab(int)));
}

int SmartTabWidget::addTab(QWidget* page, const QString& label)
{
    return addTab( page, QIcon(), label );
}

int SmartTabWidget::addTab(QWidget* page, const QIcon& icon, const QString & label)
{
    m_stack->addWidget( page );

    int index = m_tab->addTab( icon, label );
    assert( m_stack->count() == index+1 );

    return index;
}

int SmartTabWidget::currentIndex() const
{
    return m_tab->currentIndex();
}

QWidget* SmartTabWidget::widget( int index )
{
    return m_stack->widget( index );
}

void SmartTabWidget::tryToChangeTab( int index )
{
    int currentIndex = m_stack->currentIndex();
    bool canChange = true;
    emit aboutToChangeTab( m_stack->widget( currentIndex ),
                           m_stack->widget( index ),
                           &canChange );

    if ( canChange )
    {
        m_stack->setCurrentIndex( index );
    }
    else
    {
        // prevent this function to be called again
        bool blocked = m_tab->blockSignals( true );
        // unselect requested tab as change is not allowed
        m_tab->setCurrentIndex( currentIndex );
        m_tab->blockSignals( blocked );
    }
}

One can connect aboutToChangeTab to a slot (allowTabChange) and do something like:

void MyWidget::allowTabChange( QWidget* curTab, QWidget* nextTab, bool* allowed )
{
    if ( !canChange( curTab, nextTab ) )
        *allowed = false;
}
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • The drawback of this solution is that the user still sees the tab switching before they abort the process, which does not feel very good. The event filter solution proposed by @andrey.s is better, as it interrupts the tab switching from the beginning (i.e. when the mouse press event occurs) – silentspring Jul 30 '23 at 05:41