Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. connect parent's member field to slot in a dialog child class
Qt 6.11 is out! See what's new in the release blog

connect parent's member field to slot in a dialog child class

Scheduled Pinned Locked Moved Solved General and Desktop
3 Posts 2 Posters 640 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    J Offline
    johnco3
    wrote on last edited by
    #1

    I am developing a QT 6.x multimedia application (actually Qt 6.6.0 dev release, due to some worker thread affinity and windows resampler bug fixes that were fixed in the dev branch), The application consists of a MainWindow UI class that launches a modeless SettingsDialog dialog whenever I need to change the MainWindow's settings.

    My question is how can I avoid duplicating the field MainWindow::mMediaDevices in SettingsDialog::mMediaDevices while keeping the connection count under control. I think it should be possible for a dialog class to connect to a field belonging to its parent.

    The MainWindow class contains an mMediaDevices smart pointer field with which I need to make a signal slot connection with from within the dialog class in order to handle whenever a user plugs or unplugs an audio device from the system. Due to the nature of when the dialog is launched on demand and not wanting to have additional signal/slot connections -

    I need the settings dialog to be able to handle changes without requiring a duplicate mMediaDevices member

    This is the stripped down MainWindow class along with its constructor where the connections are setup to handle audio output and audio input changes to the std::unique_ptr<QMediaDevices> member.

    These connection lambdas get successfully called. It is probably worth noting that these connections are setup with a 'this' context 3rd parameter - this is to allow the connections to be auto disconnected when no longer used.

    /** Main application window */
    class MainWindow final : public QMainWindow
    {
        Q_OBJECT
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow() override;
    
    private slots:
        static void on_actionExit_triggered();
        void on_tabWidget_currentChanged(int);
        void on_esIdentCombo_currentIndexChanged(int);
    private:
        ...
        //! UI
        Ui::MainWindow *ui;
        ...
        //! Media Devices
        std::unique_ptr<QMediaDevices> mMediaDevices;
    };
    
    //! MainWindow constructor.
    MainWindow::MainWindow(QWidget* parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
        ...
        , mMediaDevices{ std::make_unique<QMediaDevices>(this) }
    {
        // figure out the correctly selected input device
        connect(mMediaDevices.get(), &QMediaDevices::audioInputsChanged, this,
            [this] {
                // TODO - do stuff
            });
        // figure out the correctly selected output device
        connect(mMediaDevices.get(), &QMediaDevices::audioOutputsChanged, this,
            [this] {
                // TODO - do stuff
            });
        ...
    }
    

    Now for the SettingsDialog class, the code below shows where the dialog is constructed from within a audioConfigButton clicked slot. As a side question; given that this code is triggered each time the audioConfigButton is clicked, do I need to do an explicitly disconnect in order manage the connection count on the dialog? Perhaps the SettingsDialog dialog(..); going out of will perform the auto disconnect?

    void
    MainWindow::on_audioConfigButton_clicked()
    {
        // get an updated set of settings from a modal dialog
        // making sure we initialize the dialog box with
        // the current settings.
        SettingsDialog dialog(this, *mpAudioSettings);
    
        dialog.setModal(false);
    
        // allow interactive setting changes while dialog box is open
        connect(&dialog, &SettingsDialog::settingsChanged,
            this, &MainWindow::handleSettingsChanged);
    
        if (dialog.exec() == QDialog::Accepted) {
            // reset stats
            resetStats();
            handleSettingsChanged(dialog.getAudioConfig(), true);
            onLogEntryAdded(level_enum::info, "Audio settings changed");
        } else {
            onLogEntryAdded(level_enum::info, "Audio settings restored");
            handleSettingsChanged(*mpAudioSettings, true);
        }
    }
    

    In order to handle the QMediaDevices audioInputsChanged and audioOutputsChanged signals, I need to duplicate the std::unique_ptr<QMediaDevices> mMediaDevices member in the settings dialog class.

    Here is a stripped down SettingsDialog class showing how I handle the mMediaDevices signals in the constructor. This works well but requires duplication of the mMediaDevices in 2 places. Given that the SettingsDialog's parent also contains a mMediaDevices field - why can I not make a signal slot lambda with this instead? I thought something line

    class SettingsDialog final : public QDialog
    {
        Q_OBJECT
    public:
        //! Explicit constructor.
        SettingsDialog(QWidget* parent = nullptr, const AudioSettings& rSettings = {});
        SettingsDialog(const SettingsDialog&) = delete;
        SettingsDialog(SettingsDialog&&) noexcept = delete;
        SettingsDialog& operator=(const SettingsDialog&) = delete;
        SettingsDialog& operator=(SettingsDialog&&) noexcept = delete;
        ...    
        //! Destructor.
        ~SettingsDialog() override;
    Q_SIGNALS:
        //! Signal a change to the settings
        void settingsChanged(const AudioSettings&, bool = false) const;
        //! Add log entry
        void addLogEntry(const spdlog::level::level_enum, const QString&) const;
    private slots:
        void on_SettingsDialog_accepted();
        void on_resetButton_clicked() const;
        void on_param1_valueChanged(int) const;
        void on_param2_valueChanged(int) const;
    private:
        ...
       Ui::SettingsDialog* ui;
        //! Media Devices
        std::unique_ptr<QMediaDevices> mMediaDevices;
        ...
    };
    
    
    SettingsDialog::SettingsDialog(
        QWidget* parent, const AudioSettings& rSettings)
        : QDialog(parent)
        , ui{ new Ui::SettingsDialog }
        ...
        , mMediaDevices{ std::make_unique<QMediaDevices>(this) }
        ...
    {
        ui->setupUi(this);
    
        ...
        // THIS IS WHAT I WOULD LIKE TO DO IF POSSIBLE
        .. NOTE THE CONNECTION FOR THE DIALOG NEEDS TO 
        // AUTO DISCONNECT WHEN THE DIALOG IS DESTRUCTED
        //connect(qobject_cast<Ui::MainWindow*>(parent)->mMediaDevices, 
        //       &Ui::MainWindow::mMediaDevices, this, [this, &rSettings] {
        //            TODO - do stuff with rSettings and the mMediaDevices
        //    });
    
        // audio input change handler - supports hot plug.
        // context parameter ensures auto disconnect.
        connect(mMediaDevices.get(), &QMediaDevices::audioInputsChanged, this,
            [this, &rSettings] {
    		// TODO - do stuff with rSettings and the mMediaDevices
            });
    
        // audio output change handler - supports hot plug.
        // context parameter ensures auto disconnect.
        connect(mMediaDevices.get(), &QMediaDevices::audioOutputsChanged, this,
            [this, &rSettings] {
    		// TODO - do stuff with rSettings and the mMediaDevices
            });
        ...
    }
    
    Christian EhrlicherC 1 Reply Last reply
    0
    • Christian EhrlicherC Christian Ehrlicher

      Use a shared_ptr and pass it to the dialog or a raw pointer since QMediaDevices is a QObject and you're passing a valid parent to it.
      and btw: Mixing shared pointers and object trees is ... problematic.

      J Offline
      J Offline
      johnco3
      wrote on last edited by johnco3
      #3

      @Christian-Ehrlicher Thanks for your quick reply. I think I found the solution. With the updated syntax below the code calls the lambda slot in the MainWindow's constructor connect and it also calls the connection slot the dialog when if it is opened while the audio inputs or outputs are changed (by unplugging a headset for example).

      Up to this point, I was having a qobject_cast errors, as I was including the wrong header and incorrectly doing forward declarations. (parent below represents my mainwindow). The following code in the constructor of the SettingsDialog child class works well and more to the point, I no longer need to have duplicated objects (one in the MainWindow and a separate one in the SettingsDialog.

        // hot plugging audio support through parent field
         auto* mediaDevices = qobject_cast<MainWindow*>(parent)->mMediaDevices.get();
      
         // audio input change handler - supports hot plug.
         // context parameter ensures auto disconnect.
         connect (mediaDevices,&QMediaDevices::audioInputsChanged, this,
             [this, &rSettings] {
                 auto* pComboBox = ui->inputDevices;
                 pComboBox->clear();
                 for (const auto& next : QMediaDevices::audioInputs()) {
                     if (!next.isNull()) {
                         pComboBox->addItem(next.description(),
                             QVariant::fromValue(next));
                     }
                 }
      
                 // add generator device
                 static auto gDevice = gGeneratorInfo->create();
                 pComboBox->addItem(gDevice.description(),
                     QVariant::fromValue(gDevice));
                 const auto settingsDeviceName =
                     rSettings.audioInput.description();
                 (void)settingsDeviceName;
                 for (auto index = 0; index < pComboBox->count(); ++index) {
                     // search for matching audio device in the settings
                     if (pComboBox->itemData(index).isValid() &&
                         pComboBox->itemData(index).value<
                         QAudioDevice>() == rSettings.audioInput) {
                         pComboBox->setCurrentIndex(index);
                         return;
                     }
                 }
      
                 // device specified in settings unplugged - replace with default device
                 pComboBox->setCurrentIndex(
                     pComboBox->findData(QVariant::fromValue(
                         QMediaDevices::defaultAudioInput())));
             });
      
         // audio output change handler - supports hot plug.
         // context parameter ensures auto disconnect.
         connect(mediaDevices, &QMediaDevices::audioOutputsChanged, this,
             [this, &rSettings] {
                 auto* pComboBox = ui->outputDevices;
                 pComboBox->clear();
                 for (const auto& next : QMediaDevices::audioOutputs()) {
                     if (!next.isNull()) {
                         pComboBox->addItem(next.description(),
                             QVariant::fromValue(next));
                     }
                 }
      
                 // add rtp device
                 static auto gDevice = gRTPDeviceInfo->create();
                 pComboBox->addItem(gDevice.description(),
                     QVariant::fromValue(gDevice));
                 const auto settingsDeviceName =
                     rSettings.audioOutput.description();
                 (void)settingsDeviceName;
                 for (auto index = 0; index < pComboBox->count(); ++index) {
                     // search for matching audio device in the settings
                     if (pComboBox->itemData(index).isValid() &&
                         pComboBox->itemData(index).value<
                         QAudioDevice>() == rSettings.audioOutput) {
                         pComboBox->setCurrentIndex(index);
                         return;
                     }
                 }
      
                 // device specified in settings unplugged - replace with default device
                 pComboBox->setCurrentIndex(
                     pComboBox->findData(QVariant::fromValue(
                         QMediaDevices::defaultAudioOutput())));
             });
      
      1 Reply Last reply
      0
      • J johnco3

        I am developing a QT 6.x multimedia application (actually Qt 6.6.0 dev release, due to some worker thread affinity and windows resampler bug fixes that were fixed in the dev branch), The application consists of a MainWindow UI class that launches a modeless SettingsDialog dialog whenever I need to change the MainWindow's settings.

        My question is how can I avoid duplicating the field MainWindow::mMediaDevices in SettingsDialog::mMediaDevices while keeping the connection count under control. I think it should be possible for a dialog class to connect to a field belonging to its parent.

        The MainWindow class contains an mMediaDevices smart pointer field with which I need to make a signal slot connection with from within the dialog class in order to handle whenever a user plugs or unplugs an audio device from the system. Due to the nature of when the dialog is launched on demand and not wanting to have additional signal/slot connections -

        I need the settings dialog to be able to handle changes without requiring a duplicate mMediaDevices member

        This is the stripped down MainWindow class along with its constructor where the connections are setup to handle audio output and audio input changes to the std::unique_ptr<QMediaDevices> member.

        These connection lambdas get successfully called. It is probably worth noting that these connections are setup with a 'this' context 3rd parameter - this is to allow the connections to be auto disconnected when no longer used.

        /** Main application window */
        class MainWindow final : public QMainWindow
        {
            Q_OBJECT
        public:
            MainWindow(QWidget *parent = nullptr);
            ~MainWindow() override;
        
        private slots:
            static void on_actionExit_triggered();
            void on_tabWidget_currentChanged(int);
            void on_esIdentCombo_currentIndexChanged(int);
        private:
            ...
            //! UI
            Ui::MainWindow *ui;
            ...
            //! Media Devices
            std::unique_ptr<QMediaDevices> mMediaDevices;
        };
        
        //! MainWindow constructor.
        MainWindow::MainWindow(QWidget* parent)
            : QMainWindow(parent)
            , ui(new Ui::MainWindow)
            ...
            , mMediaDevices{ std::make_unique<QMediaDevices>(this) }
        {
            // figure out the correctly selected input device
            connect(mMediaDevices.get(), &QMediaDevices::audioInputsChanged, this,
                [this] {
                    // TODO - do stuff
                });
            // figure out the correctly selected output device
            connect(mMediaDevices.get(), &QMediaDevices::audioOutputsChanged, this,
                [this] {
                    // TODO - do stuff
                });
            ...
        }
        

        Now for the SettingsDialog class, the code below shows where the dialog is constructed from within a audioConfigButton clicked slot. As a side question; given that this code is triggered each time the audioConfigButton is clicked, do I need to do an explicitly disconnect in order manage the connection count on the dialog? Perhaps the SettingsDialog dialog(..); going out of will perform the auto disconnect?

        void
        MainWindow::on_audioConfigButton_clicked()
        {
            // get an updated set of settings from a modal dialog
            // making sure we initialize the dialog box with
            // the current settings.
            SettingsDialog dialog(this, *mpAudioSettings);
        
            dialog.setModal(false);
        
            // allow interactive setting changes while dialog box is open
            connect(&dialog, &SettingsDialog::settingsChanged,
                this, &MainWindow::handleSettingsChanged);
        
            if (dialog.exec() == QDialog::Accepted) {
                // reset stats
                resetStats();
                handleSettingsChanged(dialog.getAudioConfig(), true);
                onLogEntryAdded(level_enum::info, "Audio settings changed");
            } else {
                onLogEntryAdded(level_enum::info, "Audio settings restored");
                handleSettingsChanged(*mpAudioSettings, true);
            }
        }
        

        In order to handle the QMediaDevices audioInputsChanged and audioOutputsChanged signals, I need to duplicate the std::unique_ptr<QMediaDevices> mMediaDevices member in the settings dialog class.

        Here is a stripped down SettingsDialog class showing how I handle the mMediaDevices signals in the constructor. This works well but requires duplication of the mMediaDevices in 2 places. Given that the SettingsDialog's parent also contains a mMediaDevices field - why can I not make a signal slot lambda with this instead? I thought something line

        class SettingsDialog final : public QDialog
        {
            Q_OBJECT
        public:
            //! Explicit constructor.
            SettingsDialog(QWidget* parent = nullptr, const AudioSettings& rSettings = {});
            SettingsDialog(const SettingsDialog&) = delete;
            SettingsDialog(SettingsDialog&&) noexcept = delete;
            SettingsDialog& operator=(const SettingsDialog&) = delete;
            SettingsDialog& operator=(SettingsDialog&&) noexcept = delete;
            ...    
            //! Destructor.
            ~SettingsDialog() override;
        Q_SIGNALS:
            //! Signal a change to the settings
            void settingsChanged(const AudioSettings&, bool = false) const;
            //! Add log entry
            void addLogEntry(const spdlog::level::level_enum, const QString&) const;
        private slots:
            void on_SettingsDialog_accepted();
            void on_resetButton_clicked() const;
            void on_param1_valueChanged(int) const;
            void on_param2_valueChanged(int) const;
        private:
            ...
           Ui::SettingsDialog* ui;
            //! Media Devices
            std::unique_ptr<QMediaDevices> mMediaDevices;
            ...
        };
        
        
        SettingsDialog::SettingsDialog(
            QWidget* parent, const AudioSettings& rSettings)
            : QDialog(parent)
            , ui{ new Ui::SettingsDialog }
            ...
            , mMediaDevices{ std::make_unique<QMediaDevices>(this) }
            ...
        {
            ui->setupUi(this);
        
            ...
            // THIS IS WHAT I WOULD LIKE TO DO IF POSSIBLE
            .. NOTE THE CONNECTION FOR THE DIALOG NEEDS TO 
            // AUTO DISCONNECT WHEN THE DIALOG IS DESTRUCTED
            //connect(qobject_cast<Ui::MainWindow*>(parent)->mMediaDevices, 
            //       &Ui::MainWindow::mMediaDevices, this, [this, &rSettings] {
            //            TODO - do stuff with rSettings and the mMediaDevices
            //    });
        
            // audio input change handler - supports hot plug.
            // context parameter ensures auto disconnect.
            connect(mMediaDevices.get(), &QMediaDevices::audioInputsChanged, this,
                [this, &rSettings] {
        		// TODO - do stuff with rSettings and the mMediaDevices
                });
        
            // audio output change handler - supports hot plug.
            // context parameter ensures auto disconnect.
            connect(mMediaDevices.get(), &QMediaDevices::audioOutputsChanged, this,
                [this, &rSettings] {
        		// TODO - do stuff with rSettings and the mMediaDevices
                });
            ...
        }
        
        Christian EhrlicherC Offline
        Christian EhrlicherC Offline
        Christian Ehrlicher
        Lifetime Qt Champion
        wrote on last edited by
        #2

        Use a shared_ptr and pass it to the dialog or a raw pointer since QMediaDevices is a QObject and you're passing a valid parent to it.
        and btw: Mixing shared pointers and object trees is ... problematic.

        Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
        Visit the Qt Academy at https://academy.qt.io/catalog

        J 1 Reply Last reply
        1
        • Christian EhrlicherC Christian Ehrlicher

          Use a shared_ptr and pass it to the dialog or a raw pointer since QMediaDevices is a QObject and you're passing a valid parent to it.
          and btw: Mixing shared pointers and object trees is ... problematic.

          J Offline
          J Offline
          johnco3
          wrote on last edited by johnco3
          #3

          @Christian-Ehrlicher Thanks for your quick reply. I think I found the solution. With the updated syntax below the code calls the lambda slot in the MainWindow's constructor connect and it also calls the connection slot the dialog when if it is opened while the audio inputs or outputs are changed (by unplugging a headset for example).

          Up to this point, I was having a qobject_cast errors, as I was including the wrong header and incorrectly doing forward declarations. (parent below represents my mainwindow). The following code in the constructor of the SettingsDialog child class works well and more to the point, I no longer need to have duplicated objects (one in the MainWindow and a separate one in the SettingsDialog.

            // hot plugging audio support through parent field
             auto* mediaDevices = qobject_cast<MainWindow*>(parent)->mMediaDevices.get();
          
             // audio input change handler - supports hot plug.
             // context parameter ensures auto disconnect.
             connect (mediaDevices,&QMediaDevices::audioInputsChanged, this,
                 [this, &rSettings] {
                     auto* pComboBox = ui->inputDevices;
                     pComboBox->clear();
                     for (const auto& next : QMediaDevices::audioInputs()) {
                         if (!next.isNull()) {
                             pComboBox->addItem(next.description(),
                                 QVariant::fromValue(next));
                         }
                     }
          
                     // add generator device
                     static auto gDevice = gGeneratorInfo->create();
                     pComboBox->addItem(gDevice.description(),
                         QVariant::fromValue(gDevice));
                     const auto settingsDeviceName =
                         rSettings.audioInput.description();
                     (void)settingsDeviceName;
                     for (auto index = 0; index < pComboBox->count(); ++index) {
                         // search for matching audio device in the settings
                         if (pComboBox->itemData(index).isValid() &&
                             pComboBox->itemData(index).value<
                             QAudioDevice>() == rSettings.audioInput) {
                             pComboBox->setCurrentIndex(index);
                             return;
                         }
                     }
          
                     // device specified in settings unplugged - replace with default device
                     pComboBox->setCurrentIndex(
                         pComboBox->findData(QVariant::fromValue(
                             QMediaDevices::defaultAudioInput())));
                 });
          
             // audio output change handler - supports hot plug.
             // context parameter ensures auto disconnect.
             connect(mediaDevices, &QMediaDevices::audioOutputsChanged, this,
                 [this, &rSettings] {
                     auto* pComboBox = ui->outputDevices;
                     pComboBox->clear();
                     for (const auto& next : QMediaDevices::audioOutputs()) {
                         if (!next.isNull()) {
                             pComboBox->addItem(next.description(),
                                 QVariant::fromValue(next));
                         }
                     }
          
                     // add rtp device
                     static auto gDevice = gRTPDeviceInfo->create();
                     pComboBox->addItem(gDevice.description(),
                         QVariant::fromValue(gDevice));
                     const auto settingsDeviceName =
                         rSettings.audioOutput.description();
                     (void)settingsDeviceName;
                     for (auto index = 0; index < pComboBox->count(); ++index) {
                         // search for matching audio device in the settings
                         if (pComboBox->itemData(index).isValid() &&
                             pComboBox->itemData(index).value<
                             QAudioDevice>() == rSettings.audioOutput) {
                             pComboBox->setCurrentIndex(index);
                             return;
                         }
                     }
          
                     // device specified in settings unplugged - replace with default device
                     pComboBox->setCurrentIndex(
                         pComboBox->findData(QVariant::fromValue(
                             QMediaDevices::defaultAudioOutput())));
                 });
          
          1 Reply Last reply
          0
          • J johnco3 has marked this topic as solved on

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved