PDA

View Full Version : QFileDialog: Enable/Disable "Open" button in inherited class not working



bmahf
13th February 2014, 20:48
I'm having an issue inheriting from QFileDialog and controlling the enabling/disabling of the Open button as needed. I've read a lot of posts online, but no one seems to be having my problem. I've tried a number of things on my own, but don't seem to be working this one out.

What I'm tasked with:

I need to be able to keep the inherited dialog's Open button disabled (unclickable) as long as the selection is not a file or not of a specified extension (which is partially handled by filtering, though All Files is an option), and also as long as the content of the selected file is not of the correct proprietary format (which is where my issue shows up). This means when the user selects a file from the list, I read a format specifier from the head of the file, and need to keep the Open button disabled as long as the format is not correct for the current run of the application.

I am very much trying to avoid a user having to get a separate "Are you sure?" dialog. I will show an error in the dialog itself, and want to only enable the Open button when a file with the correct type and format is selected.

What I've done:

I have created a class called MyFileDialog (code shown below - name changed to protect the innocent) which inherits QFileDialog and connects the QFileDialog::currentChanged() signal to a local slot function fileSelectionChanged(). The slot calls a helper function which opens and checks the file for valid format, and returns a bool for validity. The slot receives this return value and grabs the Open button (retrieved from the file dialog button box and stored at construction time) and sets the Open button's enabled state to that bool value.

When I run the program and open the file dialog and click on a file that does not have a valid format, the Open button remains enabled which is incorrect, but when I debug through the given code, I see that I do have the correct button (the text of the button shows as "&Open"), and that the button was set enabled = false (both tests shown in the code below).

I assume that there is a timing issue here, so that I'm responding to a signal, and then maybe sometime after I respond the button is reset to enabled = true by the QFileDialog itself. Not sure how to get passed this if that's the case.

I also tried overriding the mouseReleaseEvent() (code not shown). I get into this event handler when I click anywhere in the QFileDialog other than sub-widgets. If I click on any sub-widget (such as the list view, the folder drop-down, the file name text box, or any of the buttons) I don't get into the event handler at all. It seems that QFileDialog is accepting and not propagating the event. Does anybody see a way around this? I really need this to work as described.

The following code has been fully tested before submitting:



// Class declaration for MyFileDialog

#pragma once

#include <qfiledialog.h>

class MyFileDialog : public QFileDialog
{
Q_OBJECT

public:
//! \brief CTOR
MyFileDialog(QWidget* parent, QString directory);

//! \brief DTOR
virtual ~MyFileDialog() {}

protected slots:
virtual void fileSelectionChanged(const QString& file);

protected:
bool formatCheck(const QString& file);

private:
QPushButton* myOpenButton;
};




// Class definition for MyFileDialog

#include "MyFileDialog.h"
#include "qdialogbuttonbox.h"
#include "qpushbutton.h"

MyFileDialog::MyFileDialog(QWidget* parent, QString startdir)
: QFileDialog(parent, tr("Load Correct Format"), startdir),
myOpenButton(0)
{
// for an Open File dialog, get the Open button for later use
if (acceptMode() == QFileDialog::AcceptOpen)
{
QDialogButtonBox *button_box = findChild<QDialogButtonBox *>();
if (button_box) {
myOpenButton = (QPushButton *)button_box->button(QDialogButtonBox::Open);

// the following line shows that I've retrieved
// a button with the text: "&Open".
QString buttonText = myOpenButton->text();
}
}

connect(this, SIGNAL(currentChanged(const QString&)), this, SLOT(fileSelectionChanged(const QString&)));
}

void MyFileDialog::fileSelectionChanged(const QString& file)
{
QFileInfo fileInfo(file);
if (fileInfo.exists())
{
if (myOpenButton) {
bool isvalid = formatCheck(file);
myOpenButton->setEnabled(isvalid);

// the following two lines for testing. Shows the
// button's text is "&Open" and the enabled state is
// the same as the value of the "isvalid" variable.
QString buttonText = myOpenButton->text();
bool enabled = myOpenButton->isEnabled();
}
}
}

bool MyFileDialog::formatCheck(const QString& file)
{
// opens the file and returns the boolean result
// of the format check.

// true leaves the button enabled; false also leaves the button enabled
//bool result = true;
bool result = false;

return result;
}




// main() function instantiating and exec'ing MyFileDialog

#include "MyFileDialog.h"

int main(int argc, char *argv[])
{
MyFileDialog d(parent, "@C:\tmp");
d.exec();
}

anda_skoa
13th February 2014, 22:34
It could be that something else is changing the enabling state as well, e.g. the file dialog starting with button disabled and enabling it when a file is selected.

What you could try is to intercept the change of the enabling state using an event filter and revert the unwanted change when the event happens.

If you look at the documentation of the enabled property http://qt-project.org/doc/qt-4.8/qwidget.html#enabled-prop you see that any change to it is signalled via QEvent::EnabledChanged.

Your event filter would need to know, e.g. through a flag or similar, that it is you that triggered the change and just keep resetting the state to false if the flag is not set.

Cheers,
_

bmahf
14th February 2014, 15:44
Yes, that was it. I made the following changes to my code. When I grab the Open button in the constructor, I install the event filter on that button. I overrode the eventFilter() function as shown. Fixed my problem. Thanks for the help. Now I know...



MyFileDialog::MyFileDialog(QWidget* parent, QString startdir)
: QFileDialog(parent, tr("Load Correct Format"), startdir),
myOpenButton(0)
{
// constructor - on setting the myOpenButton pointer, I register
// the event filter on that button.
myOpenButton->installEventFilter(this);
}

bool MyFileDialog::eventFilter(QObject* obj, QEvent* event)
{
if (obj == myOpenButton)
{
QEvent::Type type = event->type();
bool valid = validSelection();
bool enabled = myOpenButton->isEnabled();
if (type == QEvent::EnabledChange && enabled && !valid)
{
// don't allow QFileDialog to alter the state of the
// Open button. This is to be handled locally in the
// MyFileDialog::fileSelectionChanged() function.
myOpenButton->setEnabled(false);
return true;
}
else
return false;
}

// pass unfiltered events on to the parent class
return QFileDialog::eventFilter(obj, event);
}

anda_skoa
14th February 2014, 17:34
I would suggest to check the event type right in the first if, otherwise you are calling validSelection() for every event that the button gets.
As long as validSelection() is basically just returning a boolean that is fine either way, but if you ever change it to do something more, it becomes just unnecessary overhead.

Cheers,
_

bmahf
14th February 2014, 18:57
Yeah, I had updated that myself after submitting, but thanks for catching it. The actual version of the if statement is shown in the following code block. validSelection() only returns a bool that was already figured out before this point, but it's better this way, because validSelection() never gets called if the type isn't EnabledChange, and if the button isn't currently enabled:



if (obj == myOpenButton)
{
if (event->type() == QEvent::EnabledChange &&
myOpenButton->isEnabled() &&
!validSelection())
{
// don't allow QFileDialog to alter the state of the
// Open button. This is to be handled locally in the
// MyFileDialog::fileSelectionChanged() function.
myOpenButton->setEnabled(false);
return true;
}
else
return false;
}