Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Select Folder, Include Files

I'm currently working on a qt application for Windows. The user needs to be able to select a directory to load all files from. I am having an issue related to this. It seems pretty stupid, but I keep getting the same feedback. The end users get confused by the file dialog because they navigate to the folder, but it doesn't show any files. Even though they are selecting a folder, it confuses them to not see files in the directory.

So I decided to dig into it and do some research. From what I have uncovered, it seems like there are basically 2 options. The IFileOpenDialog with FOS_PICKFOLDERS, which is what I am currently using via qt's QFileDialog. Or SHBrowseForFolder, which does work, but is pretty limited.

Am I missing any options? It seems like IFileOpenDialog that showed the files without allowing the user to select them would be ideal. Is there any way to accomplish this? I found a lot of older information saying it was not possible, but nothing definitive the is more recent.

like image 291
thecaptain0220 Avatar asked Oct 16 '22 06:10

thecaptain0220


2 Answers

Did you tried the QML file dialog FileDialog with selectFolder: true? Since the Qt5 documentation says

The implementation of FileDialog will be a platform file dialog if possible. If that isn't possible, then it will try to instantiate a QFileDialog.

it might be more user friendly for directory sets, since it uses the more native dialogs than QFileDialog. I tried it on MacOS where it shows your desired behaviour perfectly, but I don't have a windows development machine nearby, but find a minimal project attached:

main.qml:

import QtQuick 2.2
import QtQuick.Controls 1.0
import QtQuick.Dialogs 1.2

ApplicationWindow
{
    visible: true
    width: 640
    height: 480
    title: qsTr("Minimal Qml")
    FileDialog {
        id: fileDialog
        title: "Please choose a directory"
        selectFolder: true
        folder: shortcuts.home
        onAccepted: {
            console.log("You chose: " + fileDialog.fileUrls)
            Qt.quit()
        }
        onRejected: {
            console.log("Canceled")
            Qt.quit()
        }
        Component.onCompleted: visible = true
    }
}   

main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("main.qml")));
    return app.exec();
}

qml.qrc

<RCC>
    <qresource prefix="⁄">
        <file>main.qml</file>
    </qresource>
</RCC>

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(untitled1)

set(CMAKE_CXX_STANDARD 14)

find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED)


include_directories(${Qt5Widgets_INCLUDE_DIRS} ${QtQml_INCLUDE_DIRS})
add_definitions(${Qt5Widgets_DEFINITIONS} ${QtQml_DEFINITIONS} ${${Qt5Quick_DEFINITIONS}})
qt5_add_resources(QT_RESOURCES qml.qrc)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(PROJECT "MinimalQml")

configure_file(main.qml main.qml COPYONLY)

add_executable(${PROJECT} main.cpp ${QT_RESOURCES})
target_link_libraries(${PROJECT}
        Qt5::Widgets
        Qt5::Qml
        Qt5::Quick
        )
like image 61
Superlokkus Avatar answered Oct 18 '22 23:10

Superlokkus


The main issue is that, as indicated in the documentation, Windows' native file dialog doesn't support showing both files and directories when selecting directories only (check this other related answer too). For QFileDialog::FileMode::Directory:

The name of a directory. Both files and directories are displayed. However, the native Windows file dialog does not support displaying files in the directory chooser.

One workaround is to use the non-native file dialog for this kind of selection, but, personally, it looks terrible if it has to live together other native file dialogs.

Here a quick comparison of two ways of selecting a directory, using the QFileDialog::getExistingDirectory, manually by creating an instance of QFileDialog, and by using native IFileDialog:

#include <qapplication.h>
#include <qfiledialog.h>
#include <qdebug.h>
#include <Windows.h>
#include <shobjidl.h>

void using_IFileDialog()
{
  IFileOpenDialog* pFileOpen;
  HRESULT hr;

  // Create the FileOpenDialog object.
  hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
                        IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

  if (SUCCEEDED(hr)) {
    // Show the Open dialog box.
    pFileOpen->SetOptions(FOS_PICKFOLDERS | FOS_PATHMUSTEXIST);
    hr = pFileOpen->Show(NULL);

    // Get the file name from the dialog box.
    if (SUCCEEDED(hr)) {
      IShellItem* pItem;
      hr = pFileOpen->GetResult(&pItem);
      if (SUCCEEDED(hr)) {
        PWSTR pszFilePath;
        hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);

        // Display the file name to the user.
        if (SUCCEEDED(hr)) {
          MessageBox(NULL, pszFilePath, L"File Path", MB_OK);
          CoTaskMemFree(pszFilePath);
        }
        pItem->Release();
      }
    }
    pFileOpen->Release();
  }
}

int main(int argc, char* argv[])
{
  QApplication a(argc, argv);

  const auto dir_1 = QFileDialog::getExistingDirectory(nullptr, "getExistingDirectory (dirs only)");
  qDebug() << dir_1;

  QFileDialog dlg(nullptr, "QFileDialog::DontUseNativeDialog");
  dlg.setFileMode(QFileDialog::Directory);
  dlg.setOption(QFileDialog::DontUseNativeDialog);
  if (dlg.exec() == QFileDialog::Accepted) {
    const auto dir_2 = dlg.directory().absolutePath();
    qDebug() << dir_2;
  }

  using_IFileDialog();

  return 0;
}
like image 30
cbuchart Avatar answered Oct 19 '22 00:10

cbuchart