Search code examples
c++qtqfiledialog

Limit amount of files user can select in QFileDialog


Here the code:

dialog = new QFileDialog(this);
dialog->setFileMode(QFileDialog::ExistingFiles);
connect(dialog, SIGNAL(currentChanged(const QString&)),
    this, SLOT(dialogSelectionChanged(const QString&)));

void MainWindow::dialogSelectionChanged(const QString& file)
{
    QStringList selected = dialog->selectedFiles();
}

The problem is that

  • fileSelected(const QString&) and filesSelected(const QStringList&) emitted only after I press 'Open' button.
  • currentChanged(const QString&) passes only newly selected file
  • and selectedFiles() returns me in that case files that were selected at previous step. Probably dialog updates files after it emits currentChanged(const QString&).

That is why I don't know how to trace currently selected files. Another problem is that I don't know so far how to cancel selection of last selected file if user exceeds limit of allowed selections.

I have seen couple of examples where people implement custom features either with QProxyModel or with custom implementation of QFileDialog but I'm not sure what will fit my needs best.


Solution

  • This can be done if you're willing to accept a shameless hack :-)

    Assuming the following code...

    QFileDialog fd;
    

    Inspecting fd using fd.findChildren<QListView *>() reveals that it has two children that either are or inherit from QListView...

    1. QListView named listView
    2. QSidebar named sidebar

    (where QSidebar is private to Qt).

    Working on the assumption that the QListView named listView is the widget of interest you can connect a callback to its selection model...

    QFileDialog fd;
    for (const auto &i: fd.findChildren<QListView *>("listView")) {
      auto *sm = i->selectionModel();
      QObject::connect(sm, &QItemSelectionModel::selectionChanged,
                       [sm](const QItemSelection &selected, const QItemSelection &deselected)
                         {
    
                           /*
                            * Here we pass a hard-coded max selected items
                            * value of 5 to the real callback/slot.
                            */
                           handle_selection_updated(5, sm, selected, deselected);
                         });
    }
    

    Here handle_selection_updated has the following definition...

    void handle_selection_updated (int selection_max, QItemSelectionModel *sm,
                                   const QItemSelection &selected,
                                   const QItemSelection &deselected)
    {
    
      /*
       * We need to remember the last valid selection.  The following
       * is declared static in this simple case but would generally be
       * a class member in `real' code.
       */
      static QItemSelection last_selected;
    
      /*
       * Because we update the selection model `sm' from within this
       * slot this function will recurse which will cause problems if
       * we don't detect it and take appropriate action.
       */
      static bool recursing = false;
      if (recursing)
        return;
    
      /*
       * If the number of rows selected is greater than the value
       * specified by `selection_max' then revert to the last valid
       * selection as specified by `last_selected'.
       */
      if (sm->selectedRows().size() > selection_max) {
    
        /*
         * The following call to QItemSelectionModel::clearSelection
         * will result in a recursive call to this function.  Set
         * `recursing' to true to catch this and avoid associated
         * problems.
         */
        recursing = true;
    
        /*
         * Now clear the selection and reset it to the items from
         * `last_selected'.
         */
        sm->clearSelection();
        for (const auto &i: last_selected.indexes()) {
          sm->select(i, QItemSelectionModel::Select);
        }
        recursing = false;
      }
    
      /*
       * Update `last_selected'.
       */
      last_selected = sm->selection();
    }
    

    I've only tested the above code briefly but it appears to behave in the manner required.