thecaptain0220
thecaptain0220

Reputation: 2168

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.

Upvotes: 5

Views: 2601

Answers (3)

Michael Chourdakis
Michael Chourdakis

Reputation: 11178

Despite SHBrowseForFolder bugs, I would 100% prefer it, for the casual user is certain to get confused by the IFileOpenDialog when all he sees is an empty area. Even I myself get occasionally confused. It is simpler to code it as well.

I always use it with BIF_EDITBOX to allow power users to quickly type a path and, also, I always use it inside another dialog that has a preselected path and a ‘Change folder’ button.

Upvotes: 1

cbuchart
cbuchart

Reputation: 11575

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;
}

Upvotes: 1

Superlokkus
Superlokkus

Reputation: 5069

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
        )

Upvotes: 1

Related Questions