connect parent's member field to slot in a dialog child class
-
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 }); ... } -
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.@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()))); }); -
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 }); ... }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. -
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.@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()))); }); -
J johnco3 has marked this topic as solved on