I was trying out the "echo plugin" from the qt examples and i changed somethings to try out and stumbled upon an error that i cant fix.
here is the original Echo Window class
#include <QWidget>
#include "echointerface.h"
QT_BEGIN_NAMESPACE
class QString;
class QLineEdit;
class QLabel;
class QPushButton;
class QGridLayout;
QT_END_NAMESPACE
//! [0]
class EchoWindow : public QWidget
{
Q_OBJECT
public:
EchoWindow();
private slots:
void sendEcho();
private:
void createGUI();
bool loadPlugin();
EchoInterface *echoInterface; ***********
QLineEdit *lineEdit;
QLabel *label;
QPushButton *button;
QGridLayout *layout;
};
what i changed is the the line i highlighted with the stars. i changed it into A Qlist like this
QList<EchoInterface *>echoInterfaces
also changed the implementation of the function LoadPlugin() From this:-
bool EchoWindow::loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
pluginsDir.cd("plugins");
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
echoInterface = qobject_cast<EchoInterface *>(plugin);
if (echoInterface)
return true;
}
}
return false;
}
To this :-
bool ModifyWindow::loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
pluginsDir.cd("plugins");
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
foreach ( EchoInterface *a, echoInterfaces)
{
a = qobject_cast <EchoInterface *>(plugin);
}
if(!echoInterfaces.contains(NULL))
return true;
}
}
return false;
}
and thanks in advance
If my guess is correct, you'd like to store a collection of interfaces and your question is about how to read it properly.
I do understand that
assumption is the mother of all screw-ups
:) But let's not focus on the reason for doing this and just guess (again) that the EchoInterface is a base class for a variety of some available interfaces.
What's wrong in your code:
bool ModifyWindow::loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
pluginsDir.cd("plugins");
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
foreach ( EchoInterface *a, echoInterfaces)
{
// this is pointless - only a tmp variable 'a' is modified for a while
// just to be reset on the next iteration
a = qobject_cast <EchoInterface *>(plugin);
}
if(!echoInterfaces.contains(NULL)) // use nullptr instead of NULL
return true; // echoInterfaces is always empty, so it doesnt contain null
}
}
return false;
}
The changes you're loking for:
bool EchoWindow::loadPlugin()
{
QDir pluginsDir(QCoreApplication::applicationDirPath());
pluginsDir.cd("plugins");
const QStringList entries = pluginsDir.entryList(QDir::Files);
for (const QString &fileName : entries) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
// it's unnecessary to check if plugin == nullptr,
// because in this case qobject_cast returns nullptr
// and we go directly to pluginLoader.unload();
if (EchoInterface * echoIface = qobject_cast<EchoInterface *>(plugin)) {
echoInterfaces.append(echoIface); // populate the collection with a valid pointer
continue; // to avoid the pluginLoader.unload() call which would destroy the echoIface
}
pluginLoader.unload();
}
// Since we ensured that the echoInterfaces can contain only valid pointers,
// no additional checks are necessary — the fact that something is stored in the collection is enough
return !echoInterfaces.isEmpty();
// or
// return echoInterfaces.size() > 0;
// or even just
// return echoInterfaces.size();
}
In case my assumptions were wrong, then we definitely need a more detailed question.
Related
I have a program that needs to check at startup if a path to project is set. For that I have subclassed QMessageBox, added some custom stuff (like spacer to make dialog wider) and I am calling QFileDialog::getExistingDirectory to get directory.
Thing is, user can click Cancel in QFileDialog. But I want user to be returned to QMessageBox to have another chance to set path or quit program altogether. To achieve that I wrote method loop():
CustomMessageBox::loop()
{
while (true) {
this->exec();
if (this->clickedButton() == setPathButton) {
path = QFileDialog::getExistingDirectory(...);
if (!path.isEmpty()) { break; }
} else if (this->clickedButton() == quitButton) {
break;
}
}
}
Then, I have a method getPath():
CustomMessageBox::getPath()
{
loop();
return path;
}
which I call in main.cpp:
CustomMessageBox box;
QString path = box.getPath();
if (!path.isEmpty()) {
// save path, bla, bla
} else {
exit(EXIT_FAILURE)
}
This works, but is this a good practice? I am specifically asking regarding this while inside which resides method exec().
IMO disadvantages of your decision:
Non-standard usage of dialog class. Put strange loop inside some GUI method and call standard exec() method inside it, when the method can easily be called in a standard way.
Hide exec() call from "user" (another programmer)
Dual, or even triple, purpose of the one getPath() method:
show dialog
flag that the dialog was accepted/rejected via empty - non-empty string
return directory path string
I suggest inherit QDialog (why message box?):
In Degister I put on form standard Push Button setPathButton and Dialog Button Box buttonBox. Then I removed "Ok" button from the box:
#include <QtWidgets/QDialog>
#include "ui_CustomDialog.h"
class CustomDialog : public QDialog
{
Q_OBJECT
public:
CustomDialog(QWidget *parent = nullptr);
QString path() const { return m_path; };
private slots:
void on_setPathButton_clicked();
void on_buttonBox_rejected(); // You can use any pushButton instead: on_closeButton_clicked()
private:
Ui::CustomDialogClass ui;
QString m_path;
};
...
#include "CustomDialog.h"
#include <QFileDialog>
CustomDialog::CustomDialog(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
}
void CustomDialog::on_setPathButton_clicked()
{
m_path.clear();
QString dir = QFileDialog::getExistingDirectory();
if (!dir.isEmpty())
{
m_path = dir;
done(QDialog::Accepted);
}
}
// You can use any pushButton instead: void on_closeButton_clicked()
void CustomDialog::on_buttonBox_rejected()
{
reject();
}
main.cpp
#include "CustomDialog.h"
#include <QtWidgets/QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomDialog box;
int code = box.exec();
if (code == QDialog::Accepted)
{
qDebug() << box.path();
// save path, bla, bla
}
else
{
exit(EXIT_FAILURE);
}
return a.exec();
}
So this may seem like a strange setup. I have a C++ object that inherits from QObject called "MasterGuiLogic" for simplicity. It is created with a pointer to another object called "MainEventBroker" which as you might guess handles all of my applications events. The MasterGuiLogic object is registered with qml as a context property so that it's properties can be used anywhere in my qml. So main.cpp looks like this:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MasterEventBroker *MainEventBroker = new MasterEventBroker();
MasterGuiLogic *MainGuiLogic = new MasterGuiLogic(*MainEventBroker);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("MasterGuiLogic", &(*MainGuiLogic));
engine.load(QUrl(QLatin1String("qrc:/QmlGui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
MasterGuiLogic creates an instance of another class called SerialCom, which is set as a Q_PROPERTY so that it's properties and public slots can be reached in qml through the MasterGuiLogic property.
MasterGuiLogic.h:
class MasterGuiLogic : public QObject
{
Q_OBJECT
Q_PROPERTY(GuiLogic_SerialCom* serialCom READ serialCom CONSTANT)
public:
MasterEventBroker *eventBroker;
explicit MasterGuiLogic(MasterEventBroker &ev, QObject *parent = nullptr);
GuiLogic_SerialCom* serialCom() const {
return Gui_SerialCom;
}
private:
GuiLogic_SerialCom *Gui_SerialCom;
MasterGuiLogic.cpp:
MasterGuiLogic::MasterGuiLogic(MasterEventBroker &ev, QObject *parent) : QObject(parent)
{
this->eventBroker = &ev;
this->Gui_SerialCom = new GuiLogic_SerialCom(this);
}
SerialCom.h:
//Forward Declare our parent
class MasterGuiLogic;
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList portNames READ portNames NOTIFY portNamesChanged)
Q_PROPERTY(bool connectedToPort READ connectedToPort NOTIFY connectedToPortChanged)
public:
MasterGuiLogic *parent;
explicit GuiLogic_SerialCom(MasterGuiLogic *parent = nullptr);
std::map<QString, QSerialPortInfo> portsMap;
QStringList portNames() {
return _portNames;
}
bool connectedToPort() {
return _connectedToPort;
}
private:
QStringList _portNames;
bool _connectedToPort = false;
signals:
void portNamesChanged(const QStringList &);
void connectedToPortChanged(const bool &);
public slots:
void connectToPort(const QString portName);
void disconnectFromPort(const QString portName);
};
SerialCom.cpp:
GuiLogic_SerialCom::GuiLogic_SerialCom(MasterGuiLogic *parent) : QObject(qobject_cast<QObject *>(parent))
{
this->parent = parent;
QList<QSerialPortInfo> allPorts = QSerialPortInfo::availablePorts();
for (int i = 0; i < allPorts.size(); ++i) {
this->_portNames.append(allPorts.at(i).portName());
this->portsMap[allPorts.at(i).portName()] = allPorts.at(i);
}
emit portNamesChanged(_portNames);
}
void GuiLogic_SerialCom::connectToPort(const QString portName) {
//TODO: Connect To Port Logic Here;
//Set Connected
this->_connectedToPort = true;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
void GuiLogic_SerialCom::disconnectFromPort(const QString portName) {
//TODO: DisConnect To Port Logic Here;
//Set DisConnected
this->_connectedToPort = false;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
So from qml it's pretty easy to read any of these properties and even send signals from qml to c++
For example, this works just fine:
connectCom.onClicked: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
MasterGuiLogic.serialCom.connectToPort(comPort.currentText);
} else {
MasterGuiLogic.serialCom.disconnectFromPort(comPort.currentText);
}
}
The problem is, I can't seem to find a way to connect to signals that are emitted from SerialCom. I thought I would be able to do something like this:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
connectCom.text = "Disconnect";
comPort.enabled = false;
} else {
connectCom.text = "Connect";
comPort.enabled = true;
}
}
}
This should listen to the boolean property on SerialCom to change, but I get the following error:
QQmlApplicationEngine failed to load component
qrc:/QmlGui/main.qml:21 Type Page1 unavailable
qrc:/QmlGui/Page1.qml:49 Invalid attached object assignment
This just means that I can't "connect" using the target line above. Is there any other way I can connect to signals from a Q_PROPERTY of type QObject inside a ContextProperty?
First of all what should &(*MainGuiLogic) mean?
You are dereferencing and referencing again the MainGuiLogic? Why?
context->setContextProperty("MasterGuiLogic", MainGuiLogic); will be enought.
But registering MasterGuiLogic as Type and adding the Object named MasterGuiLogic can overide themself in QML world.
Set it like context->setContextProperty("MyGuiLogic", MainGuiLogic); to eleminate this behavior.
Also don't pass references between C++ and QML worlds like:
void connectedToPortChanged(**const bool &**);.
Just use atomic type and values (const bool);
and give it a name, to be able to use it as named value in QML:
void connectedToPortChanged(bool connected)
Here is an example with the structure like yours, which works. Just click in window and look in output console.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QObject>
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
public:
GuiLogic_SerialCom(){}
signals:
void connectedToPortChanged(bool connected);
public slots:
void connectToPort(const QString & portName);
};
class MasterGuiLogic : public QObject
{
Q_OBJECT
public:
MasterGuiLogic();
Q_PROPERTY(GuiLogic_SerialCom * serialCom READ serialCom CONSTANT)
GuiLogic_SerialCom* serialCom() const {return test;}
Q_INVOKABLE void generate_signal();
private:
GuiLogic_SerialCom * test;
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QDebug>
MasterGuiLogic::MasterGuiLogic()
{
this->test = new GuiLogic_SerialCom();
}
void MasterGuiLogic::generate_signal()
{
qDebug() << __FUNCTION__ << "Calling serialcom to gen signal";
this->test->connectToPort("88");
}
void GuiLogic_SerialCom::connectToPort(const QString &portName)
{
qDebug() << __FUNCTION__ << "got signal" << portName;
emit this->connectedToPortChanged(true);
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "test.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new MasterGuiLogic());
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import GrblCom 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea
{
anchors.fill: parent
onClicked:
{
Test.generate_signal();
}
}
Connections
{
target: Test.serialCom
onConnectedToPortChanged:
{
console.log("Got signal from SerialCom in QML. passed bool value is: " + connected);
}
}
}
Ok, I found the problem... The provided answers, while indeed helpful for other reasons, were not the correct solution. After going over the code by #Xplatforms, I couldn't figure out what the difference was between what I was doing and what he did.... until I saw this in my own code:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
There isn't supposed to be a colon(:) there...
Connections {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
Never try programming while sleepy...lol
I'm pretty new to c++ and qt. I'm not sure if i use the right terminology describe what I want to achieve. But here it goes.
My application spawns and removes widgets in a gridlayout when the user pushes buttons. Managed to do this successfully. However when the user uses the spawned widgets I want the widgets to interact with each other.
QList<QLineEdit*> m_ptrLEPathList;
QList<QPushButton*> m_ptrPBList;
qint8 m_noFields;
void MainWindow::on_pbIncFields_clicked()
{
//create widgets and place on a new row in a gridLayout
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
//storing pointers in lists to be able to delete them later.
m_ptrLEPathList.append(lineEditPath);
m_ptrPBList.append(pushButton);
ui->gridLayout->addWidget(m_ptrLEPathList.last(),m_noFields,0);
ui->gridLayout->addWidget(m_ptrPBList.last(),m_noFields,1);
connect(m_ptrPBList.last(), SIGNAL(clicked(bool), this, SLOT(on_addPath()));
m_noFields++;
}
void MainWindow::on_pbDecFields()
{
//delete last spawned widgets
}
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
//somehow set the text of the line edit spawned on the same row as the pushbutton
}
So my slot is executed when I push any spawned button but I have no idea how to store the data from the file dialog in the related lineEdit.
Is the basic idea of what I'm trying to do ok or is there any other solution to achieve the fuctionality I'm looking for?
In on_addPath slot you can use QObject::sender method to get the clicked button, and, assuming m_ptrLEPathList and m_ptrPBList lists are equal, you can easily get the corresponding QLineEdit:
void MainWindow::on_addPath()
{
QFileDialog dialog;
if (!dialog.exec())
{
return;
}
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
QPushButton *btn = qobject_cast<QPushButton*>(sender());
if (!btn)
{
return;
}
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
int index = m_ptrPBList.indexOf(btn);
if (index == -1)
{
return;
}
QLineEdit *edit = m_ptrLEPathList.at(index);
edit->setText(fileNames.first());
}
You are including 'on_addPath' function out of the scope of the 'MainWindow' class, so when the slot is called you have not access to member elements in the class.
Try to include the slot function into the class and check if you have direct access to the member elements. Also, the 'lineEditPath' element must be a member object, so it must be included into the class definition.
Something like this:
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
m_lineEditPath->setText(fileNames.first());
}
First off, void on_addPath() must be void MainWindow::on_addPath()
As for linking the data from QFileDialog it is simple. Try this:
void MainWindow::on_addPath() {
/* Get the push button you clicked */
QPushButon *btn = qobject_cast<QPushButton*>( sender() );
/* Make sure both the lists have the same size */
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
/* If the sender is a button in your list */
if ( m_ptrPBList.contains( btn ) ) {
/* Get the index of your push button */
int idx = m_ptrPBList.indexOf( btn );
/* Get the corresponding line edit */
QLineEdit *le = m_ptrLEPathList.at( idx );
/* Get your path */
QString path = QFileDialog::getOpenFileName( this, "Caption", "Default Location" );
/* If you path is not empty, store it */
if ( not path.isEmpty() )
le->setText( path );
}
}
Add a map to private section
QMap<QPushButton*, QLineEdit*> map;
Then
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
map.insert(pushButton, lineEditPath);
You can use sender() method like follow:
void on_addPath()
{
QFileDialog getPath();
getPath.exec();
QObject* obj = sender();
QPushButton *pb = 0;
if((pb = qobject_cast<QPushButton *>(obj)) != 0) {
QLineEdit* lineEdit = map->value(pb, 0);
if( lineEdit != 0 )
lineEdit->setText( getPath.<some function to get selected file name> );
}
}
I think the cleanest solution would be to contain the QLineEdit and QPushButton in a custom widget class, if it suits your project. That way you could use the file dialog inside this class, and you won't have to store the widgets in lists. It is hard to give you all the information, as you didn't really provide any details what your application is supposed to do. But in any case, the custom widget class would look something like this (you should define all the functions inside a .cpp file):
#ifndef WIDGETCONTAINER_H
#define WIDGETCONTAINER_H
#include <QWidget>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QFileDialog>
class WidgetContainer : public QWidget
{
Q_OBJECT
public:
WidgetContainer(QWidget *parent = 0) : QWidget(parent)
{
setLayout(new QHBoxLayout);
button.setText("BUTTON");
layout()->addWidget(&lineEdit);
layout()->addWidget(&button);
connect(&button, SIGNAL(clicked()), this, SLOT(buttonPressed()));
}
private:
QLineEdit lineEdit;
QPushButton button;
private slots:
void buttonPressed()
{
QString filename = QFileDialog::getSaveFileName();
lineEdit.setText(filename);
}
};
#endif // WIDGETCONTAINER_H
Is it possible to show a QFileDialog where the user can select a file or a directory, either one?
QFileDialog::getOpenFileName() accepts only files, while QFileDialog::getExistingDirectory() is directories-only, but I need to show a file dialog that can accept both a file or a directory (it makes sense for my program). QFileDialog::Options didn't have anything promising.
QFileDialog currently does not support this. I think the main problem for you here is that the FileMode is not a Q_FLAGS and the values are not power of 2, either, and so, you cannot write this to solve this issue.
setFileMode(QFileDialog::Directory|QFileDialog::ExistingFiles)
To solve this, you would need quite a bit of fiddling, e.g.:
Override the open button click operation.
Get the "treeview" indices properly for both files and directories.
My attempt below demonstrates the former, but I did not really go as far as solving the second because that seems to involve some more fiddling with the selection model.
main.cpp
#include <QFileDialog>
#include <QApplication>
#include <QWidget>
#include <QTreeWidget>
#include <QPushButton>
#include <QStringList>
#include <QModelIndex>
#include <QDir>
#include <QDebug>
class FileDialog : public QFileDialog
{
Q_OBJECT
public:
explicit FileDialog(QWidget *parent = Q_NULLPTR)
: QFileDialog(parent)
{
setOption(QFileDialog::DontUseNativeDialog);
setFileMode(QFileDialog::Directory);
// setFileMode(QFileDialog::ExistingFiles);
for (auto *pushButton : findChildren<QPushButton*>()) {
qDebug() << pushButton->text();
if (pushButton->text() == "&Open" || pushButton->text() == "&Choose") {
openButton = pushButton;
break;
}
}
disconnect(openButton, SIGNAL(clicked(bool)));
connect(openButton, &QPushButton::clicked, this, &FileDialog::openClicked);
treeView = findChild<QTreeView*>();
}
QStringList selected() const
{
return selectedFilePaths;
}
public slots:
void openClicked()
{
selectedFilePaths.clear();
qDebug() << treeView->selectionModel()->selection();
for (const auto& modelIndex : treeView->selectionModel()->selectedIndexes()) {
qDebug() << modelIndex.column();
if (modelIndex.column() == 0)
selectedFilePaths.append(directory().absolutePath() + modelIndex.data().toString());
}
emit filesSelected(selectedFilePaths);
hide();
qDebug() << selectedFilePaths;
}
private:
QTreeView *treeView;
QPushButton *openButton;
QStringList selectedFilePaths;
};
#include "main.moc"
int main(int argc, char **argv)
{
QApplication application(argc, argv);
FileDialog fileDialog;
fileDialog.show();
return application.exec();
}
main.pro
TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp
Build and Run
qmake && make && ./main
Quite old question but I think I have a simpler solution than most for the lazy ones.
You can connect the signal currentChanged of QFileDialog with a function that dynamically changes the fileMode.
//header
class my_main_win:public QMainWindow
{
private:
QFileDialog file_dialog;
}
//constructor
my_main_win(QWidget * parent):QMainWindow(parent)
{
connect(&file_dialog,QFileDialog::currentChanged,this,[&](const QString & str)
{
QFileInfo info(str);
if(info.isFile())
file_dialog.setFileMode(QFileDialog::ExistingFile);
else if(info.isDir())
file_dialog.setFileMode(QFileDialog::Directory);
});
}
And then simply call file_dialog as you would.
if(file_dialog.exec()){
QStringList dir_names=file_dialog.selectedFiles():
}
What worked for me was to use:
file_dialog.setProxyModel(nullptr);
as suggested here, or
class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
return (fileModel!=NULL && fileModel->isDir(sourceModel()->index(sourceRow, 0, sourceParent))) || QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
};
...
FileFilterProxyModel* proxyModel = new FileFilterProxyModel;
file_dialog.setProxyModel(proxyModel);
as suggested here, or
class FileDialog : public QFileDialog
{
Q_OBJECT
public:
explicit FileDialog(QWidget *parent = Q_NULLPTR)
: QFileDialog(parent)
{
m_btnOpen = NULL;
m_listView = NULL;
m_treeView = NULL;
m_selectedFiles.clear();
this->setOption(QFileDialog::DontUseNativeDialog, true);
this->setFileMode(QFileDialog::Directory);
QList<QPushButton*> btns = this->findChildren<QPushButton*>();
for (int i = 0; i < btns.size(); ++i) {
QString text = btns[i]->text();
if (text.toLower().contains("open") || text.toLower().contains("choose"))
{
m_btnOpen = btns[i];
break;
}
}
if (!m_btnOpen) return;
m_btnOpen->installEventFilter(this);
//connect(m_btnOpen, SIGNAL(changed()), this, SLOT(btnChanged()))
m_btnOpen->disconnect(SIGNAL(clicked()));
connect(m_btnOpen, SIGNAL(clicked()), this, SLOT(chooseClicked()));
m_listView = findChild<QListView*>("listView");
if (m_listView)
m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_treeView = findChild<QTreeView*>();
if (m_treeView)
m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
}
QStringList selectedFiles()
{
return m_selectedFiles;
}
bool eventFilter( QObject* watched, QEvent* event )
{
QPushButton *btn = qobject_cast<QPushButton*>(watched);
if (btn)
{
if(event->type()==QEvent::EnabledChange) {
if (!btn->isEnabled())
btn->setEnabled(true);
}
}
return QWidget::eventFilter(watched, event);
}
public slots:
void chooseClicked()
{
QModelIndexList indexList = m_listView->selectionModel()->selectedIndexes();
foreach (QModelIndex index, indexList)
{
if (index.column()== 0)
m_selectedFiles.append(this->directory().absolutePath() + "/" + index.data().toString());
}
QDialog::accept();
}
private:
QListView *m_listView;
QTreeView *m_treeView;
QPushButton *m_btnOpen;
QStringList m_selectedFiles;
};
as suggested here. Credits for the original authors and me.
Connect to the currentChanged signal and then set the file mode to the currently selected item (directory or file). This is a Python3 implementation:
class GroupFileObjectDialog(QFileDialog):
def __init__(self, parent):
super().__init__(parent)
self.setOption(QFileDialog.DontUseNativeDialog)
self.setFileMode(QFileDialog.Directory)
self.currentChanged.connect(self._selected)
def _selected(self,name):
if os.path.isdir(name):
self.setFileMode(QFileDialog.Directory)
else:
self.setFileMode(QFileDialog.ExistingFile)
Tested on PyQt 5.14 running on linux / Ubuntu18.04.
I was wondering if anybody could tell me whats actually going on here. I have included the header and implementation file for a simple config dialog. The problem is in the updateAutoSaveGroupBox slot I cannot access or change any properties of my widgets on the page. I.E. I want to make some widgets disabled if a check box has not been checked but when I try to set them I get a read access violation. Any help with this matter would be greatly appreciated. The problems exsist in these two lines(commented out so it will run for now without throwing an exception).
//autoSaveLabel->setDisabled(autoSaveIsEnabled);
//autoSaveSpinBox->setDisabled(getAutoSaveIsEnabled());
configWidget.h
class EnigmaConfigGeneralEnvironmentWidget : public QWidget
{
Q_OBJECT
public:
explicit EnigmaConfigGeneralEnvironmentWidget(QWidget *parent = 0);
~EnigmaConfigGeneralEnvironmentWidget();
signals:
void setAutoSaveIsEnabledSignal(bool autoSaveIsEnabled);
public slots:
void setAutoSaveIsEnabled(bool autoSaveIsEnabled){_AutoSaveIsenabled = autoSaveIsEnabled;}
bool getAutoSaveIsEnabled(){return _AutoSaveIsenabled;}
void updateAutoSaveGroupBox(bool autoSaveIsEnabled);
private:
void makeConnections();
void readSettings();
void writeSettings();
void createMainWidget();
QGroupBox *uiGroupBox;
QStringList localList;
QLabel *localLabel;
QComboBox *localeComboBox;
QHBoxLayout *localSelectionHLayout;
QGroupBox *systemGroupBox;
QCheckBox *autoSaveCheckBox;
QLabel *autoSaveLabel;
QSpinBox *autoSaveSpinBox;
QHBoxLayout *autoSaveHLayout;
bool _AutoSaveIsenabled;
};
ConfigWidget.cpp
#include "enigmaconfiggeneralenvironmentwidget.h"
#include <QtWidgets>
EnigmaConfigGeneralEnvironmentWidget::EnigmaConfigGeneralEnvironmentWidget(QWidget *parent) :
QWidget(parent)
{
makeConnections();
readSettings();
createMainWidget();
}
EnigmaConfigGeneralEnvironmentWidget::~EnigmaConfigGeneralEnvironmentWidget()
{
writeSettings();
}
void EnigmaConfigGeneralEnvironmentWidget::makeConnections()
{connect(this,SIGNAL(setAutoSaveIsEnabledSignal(bool)),this,SLOT(setAutoSaveIsEnabled(bool)) );
connect(this,SIGNAL(setAutoSaveIsEnabledSignal(bool)),this,SLOT(updateAutoSaveGroupBox(bool)) );
}
void EnigmaConfigGeneralEnvironmentWidget::readSettings()
{
QSettings settings;
settings.beginGroup(tr("UI.Config.Environment.General"));
bool autoSaveIsEnabled = settings.value("autoSaveIsEnabled",bool(true)).toBool();
setAutoSaveIsEnabledSignal(autoSaveIsEnabled);
settings.endGroup();
}
void EnigmaConfigGeneralEnvironmentWidget::writeSettings()
{
QSettings settings;
settings.beginGroup(tr("UI.Config.Environment.General"));
settings.setValue("autoSaveIsEnabled",getAutoSaveIsEnabled());
settings.endGroup();
}
void EnigmaConfigGeneralEnvironmentWidget::createMainWidget()
{
localList.append(tr("Danish - ???"));
localList.append(tr("English - Australia"));
localList.append(tr("English - Canada"));
localList.append(tr("English - USA"));
localList.append(tr("English - UK"));
localList.append(tr("Finnish - Finland"));
localList.append(tr("French - Canada"));
localList.append(tr("French - France"));
localList.append(tr("Norwegian - ???"));
localList.append(tr("Swedish - ???"));
uiGroupBox = new QGroupBox();
uiGroupBox->setTitle(tr("UI Settings"));
localLabel= new QLabel();
localLabel->setText(tr("Select a language: "));
localeComboBox = new QComboBox();
localeComboBox->addItems(localList);
localSelectionHLayout = new QHBoxLayout(uiGroupBox);
localSelectionHLayout->addWidget(localLabel);
localSelectionHLayout->addWidget(localeComboBox);
systemGroupBox = new QGroupBox();
systemGroupBox->setTitle(tr("System Settigns"));
autoSaveCheckBox = new QCheckBox();
autoSaveCheckBox->setText(tr("Auto-Save Enabled: "));
autoSaveCheckBox->setChecked(getAutoSaveIsEnabled());
connect(autoSaveCheckBox,SIGNAL(clicked(bool)),this,SIGNAL(setAutoSaveIsEnabledSignal(bool)));
autoSaveLabel = new QLabel(this);
autoSaveLabel->setText(tr("Auto-Save Interval is Every: "));
autoSaveSpinBox = new QSpinBox();
autoSaveSpinBox->setSuffix(tr("Mins."));
autoSaveSpinBox->setAccelerated(true);
autoSaveHLayout = new QHBoxLayout(systemGroupBox);
autoSaveHLayout->addWidget(autoSaveCheckBox);
autoSaveHLayout->addWidget(autoSaveLabel);
autoSaveHLayout->addWidget(autoSaveSpinBox);
QVBoxLayout *vLayout = new QVBoxLayout(this);
vLayout->addWidget(uiGroupBox);
vLayout->addWidget(systemGroupBox);
}
void EnigmaConfigGeneralEnvironmentWidget::updateAutoSaveGroupBox(bool autoSaveIsEnabled)
{
qDebug() << "debug " << autoSaveIsEnabled;
//autoSaveLabel->setDisabled(autoSaveIsEnabled);
//autoSaveSpinBox->setDisabled(getAutoSaveIsEnabled());
}
Spotted it:
void EnigmaConfigGeneralEnvironmentWidget::readSettings()
{
QSettings settings;
settings.beginGroup(tr("UI.Config.Environment.General"));
bool autoSaveIsEnabled = settings.value("autoSaveIsEnabled",bool(true)).toBool();
setAutoSaveIsEnabledSignal(autoSaveIsEnabled);
settings.endGroup();
}
You are calling void setAutoSaveIsEnabledSignal(bool) here (the signal), not void setAutoSaveIsEnabled(bool) (the actual setter). Hence the member variable is still uninitialized.
Reminder for yourself: Don't name signals as if they were setters, use e.g. void autoSaveIsEnabledChanged(bool)