I'm experiencing an issue with a custom QWidget
/QSplitter
implementation where the handle button for the righthand widget does not disappear when the widget's visibility is toggled off (using QWidget::hide()
or show()
).
The issue only occurs with the righthand widget and not with the lefthand widget.
Repo: https://github.com/fairybow/CollapsibleSplitter
The splitter widget here is a container meant to hold both a real QSplitter
subclass (TrueSplitter
) as well as 1 QPushButton
per visibile QSplitterHandle
and coordinate their movements).
When I run my code and toggle off the righthand widget using its toggle button, the righthand widget disappears but its corresponding handle button remains visible. The issue does not occur when toggling off the lefthand widget.
I've tried replacing the righthand widget (Preview
) with other widgets (e.g., a second QTreeView
), but the issue still persists. I'm not sure what in my code is causing this issue or how to fix it.
Working backward:
struct Meta {
int widgetIndex;
std::optional<QPushButton*> handleButton;
State state = State::Expanded;
int expandedWidth = -1;
QPushButton* button() { return handleButton.has_value() ? handleButton.value() : nullptr; }
};
Splitter(Qt::Orientation orientation, QVector<QWidget*> widgets, QWidget* parent)
: QWidget(parent)
{
/* etc. */
connect(m_trueSplitter, &TrueSplitter::widgetVisibilityChanged, this, &Splitter::showOrHideButtons);
}
void showOrHideButtons(int widgetIndex, TrueSplitter::WidgetWas visibility)
{
for (auto& m_meta : m_metas) {
if (m_meta.widgetIndex != widgetIndex) continue;
auto button = m_meta.button();
if (button == nullptr) continue;
(visibility == TrueSplitter::WidgetWas::Hidden) ? button->hide() : button->show();
}
}
class TrueSplitter : public QSplitter
{
Q_OBJECT
public:
enum class WidgetWas {
Hidden,
Shown
};
TrueSplitter(QWidget* parent) : QSplitter(parent) {}
signals:
void resized();
void widgetVisibilityChanged(int widgetIndex, WidgetWas visibility);
protected:
virtual void childEvent(QChildEvent* event) override
{
if (event->type() == QEvent::ChildAdded && event->child()->isWidgetType())
installFilters();
QSplitter::childEvent(event);
}
virtual bool eventFilter(QObject* object, QEvent* event) override
{
if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
for (auto i = 0; i < count(); ++i) {
if (!widget(i)->isVisible()) {
emit widgetVisibilityChanged(i, WidgetWas::Hidden);
qDebug() << "!!!" << widget(i) << ": eventFilter widgetVisibilityChanged signal (Hidden)";
}
else {
emit widgetVisibilityChanged(i, WidgetWas::Shown);
qDebug() << "!!!" << widget(i) << ": eventFilter widgetVisibilityChanged signal (Shown)";
}
}
for (int i = 0; i < count(); ++i) {
auto widget = this->widget(i);
(widget == nullptr)
? qDebug() << "Widget at index" << i << "has been removed from the QSplitter"
: qDebug() << "Widget at index" << i << "is still a child of the QSplitter";
}
}
return QSplitter::eventFilter(object, event);
}
virtual void resizeEvent(QResizeEvent* event) override
{
QSplitter::resizeEvent(event);
emit resized();
}
virtual QSplitterHandle* createHandle() override { return new SplitterHandle(orientation(), this); }
private:
void installFilters()
{
for (auto i = 0; i < count(); ++i)
widget(i)->installEventFilter(this);
}
};
Does anyone have any ideas or suggestions for how to troubleshoot this issue?
The problem seems to be the order in which things are happening.
Apparently childEvent is called when a widget is added, but before that process is finished. Therefore, when you call installFilters, the iteration only sees all the widgets which were added previously and (re-)installs the filter in those. The new widget is ignored.
Luckily, QChildEvent provides the newly added child via the child member function, so you do have to loop at all:
virtual void childEvent(QChildEvent* event) override
{
if (event->type() == QEvent::ChildAdded && event->child()->isWidgetType())
event->child()->installEventFilter(this);
QSplitter::childEvent(event);
}