I have a QGraphicsView subclass where, on context menu on an item, I want to show a Properties dialog.
I would like the dialog to be centered in the view...
As I have it now, with no parent, it is shown in the center of the screen.
MyView::MyView(QWidget *parent) : QGraphicsView(parent) {}
void MyView::showProperties()
{
TabDialog *tabDialog = new TabDialog(); // shows in center of screen
// TabDialog *tabDialog = new TabDialog(this); // doesn't show at all
// TabDialog *tabDialog = new TabDialog((QWidget*)this->parent()); // doesn't show at all
tabDialog->setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);
tabDialog->exec();
delete tabDialog;
}
The view is placed in a groupbox... so the parent is the groupbox...
How can I call the dialog using a parent ?
You have to set the dialog to be a top-level window, not simply a child widget that would be embedded in your view. Your setWindowsFlags call resets the relevant flags from the dialog. You need to manually preserve them.
You also should never use exec() to reenter the event loop since this requires that a lot of your other code needs to be reentrant as well.
A minimal fix would look like:
void MyView::showProperties()
{
auto dialog = new TabDialog(this);
dialog->setWindowFlags(Qt::Dialog |
Qt::WindowCloseButtonHint |
Qt::WindowSystemMenuHint);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
Perhaps you'd want to cache the dialog:
class MyView : public ... {
QPointer<TabDialog> m_tabDialog; // nulls itself when the dialog perishes
QTimer m_tabDialogTimer;
...
};
MyView::MyView(...) {
m_tabDialogTimer->setSingleShot(true);
...
}
void MyView::showProperties() {
if (! m_tabDialog) {
m_tabDialog = new TabDialog(this);
m_tabDialog->setWindowFlags(Qt::Dialog |
Qt::WindowCloseButtonHint |
Qt::WindowSystemMenuHint);
QObject::connect(&m_tabDialogTimer, &QTimer::timeout,
m_tabDialog, &QObject::deleteLater);
QObject::connect(m_tabDialog, &QDialog::finished, [&this](int){
// the dialog gets deleted 120 seconds after last use
m_tabDialogTimer.start(120);
});
}
m_tabDialogTimer.stop(); // reset pending timeout, if any
m_tabDialog->show();
}
Related
I have a button in my Custom QDialog, I am emitting a signal when pushbutton 1 is clicked
void MyCustomDialog::on_pushButton_1()
{
this->hide(); //i need to hide this window before 'OnButton_1_Clicked' stuffs starts
emit button_1_clicked();
}
In my main window I have connected the slot and created the instance as shown below
void MainWindow::MainWindow()
{
MyCustomDialog *dlg = MyCustomDialog::getInstance(this); //only single instance created
connect(dlg, &MyCustomDialog::button_1_clicked, this, &MainWindow::OnButton_1_Clicked);
}
I am displaying my custom dialog from a function in mainwindow as below
void MainWindow::dispayCustomDialog()
{
MyCustomDialog *dlg = MyCustomDialog::getInstance();
dlg->show();
}
Below shows how my 'OnButton_1_Clicked' slot. In which I am capturing the screenshot using below line
void MainWindow::OnButton_1_Clicked()
{
//capture the screen shot
QScreen *screen = QGuiApplication::primaryScreen();
QPixmap *map = new QPixmap(screen->grabWindow(0));
bool result = map->save("D:/test.jpg", "JPG");
}
Once I captured screen using above function, I can still see my 'MyCustomDialog' in test.jpg file. Qt doc says QGuiApplication::primaryScreen captures the initial state of application. So i think, this is expected in my case. Do we have any other solution to grab screen with current state ?
What I am trying to achieve is grab the screen in OnButton_1_Clicked() function after hiding my 'MyCustomDialog'.
I found a solution. Used a singleslot timer with delay of 500 msec before capturing the screen as below. This wait for my custom dialog to hide properly.
void MainWindow::OnButton_1_Clicked()
{
QTimer::singleShot(500, this, &MainWindow::shootscreen);
}
void MainWindow::shootscreen()
{
//capture the screen shot
QScreen *screen = QGuiApplication::primaryScreen();
QPixmap map = screen->grabWindow(0);
bool result = map.save("D:/test.jpg", "JPG");
}
Is there any way to check that the ui elements(line edit,combo box,etc.) of the dialog has been changed.
What I want is to show a message to the user if he changes the value of any single ui element, saying that details have been partially filled.
What i can do is use connect for each ui element & based on the value changed of each element i am setting a boolean flag & at the time of close event i am checking that boolean flag.
But Its quite complicate to check it for each widget.
Is there any easier way.
Code that I am using for single ui element is,
connect(ui->leAge,SIGNAL(textChanged(QString)),this,SLOT(functChanged())); //In Constructor
void DemoDialog::functChanged() //Will be called if value of line edit (ui->leAge) is changed
{
flag=true;
}
void DemoDialog::closeEvent(QCloseEvent *event)
{
if (flag) {
if (QMessageBox::warning(this,"Close","Do you want to close?",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes) {
this->close();
}
}
You can't reimplement closeEvent to prevent closing a window. The close() call that you do is either redundant or an error (infinite recursion), since a closeEvent method call is just a way of being notified that a closing is imminent. At that point it's too late to do anything about it.
Keep in mind the following:
Closing a dialog usually is equivalent to canceling the dialog. Only clicking OK should accept the changes.
When a user wants to close a dialog, you don't have to ask them about it. They initiated the action. But:
It is proper to ask a user about dialog closure if there are changes have not been accepted - on platforms other than OS X.
So, you have to do several things:
Reimplement the void event(QEvent*) method. This allows you to reject the close event.
Offer Apply/Reset/Cancel buttons.
Your flag approach can be automated. You can find all the controls of the dialog box and set the connections automatically. Repeat the statement below for every type of control - this gets tedious rather quickly:
foreach(QTextEdit* w, findChildren<QTextEdit*>())
connect(w, SIGNAL(textChanged(QString)), SLOT(functChanged()));
You can leverage the meta property system. Most controls have a user property - that's the property that holds the primary value of the control (like text, selected item, etc). You can scan all of the widget children, and connect the property change notification signal of the user property to your flag:
QMetaMethod slot = metaObject().method(
metaObject().indexOfSlot("functChanged()"));
foreach (QWidget* w, findChildren<QWidget*>()) {
QMetaObject mo = w->metaObject();
if (!mo.userProperty().isValid() || !mo.userProperty().hasNotifySignal())
continue;
connect(w, mo.notifySignal(), this, slot);
}
Each widget is a QObject. QObjects can have properties, and one of the properties can be declared to be the user property. Most editable widget controls have such a property, and it denotes the user input (text, numerical value, selected index of the item, etc.). Usually such properties also have change notification signals. So all you do is get the QMetaMethod denoting the notification signal, and connect it to your function that sets the flag.
To determine the changed fields, you don't necessarily need a flag. In many dialog boxes, it makes sense to have a data structure that represent the data in the dialog. You can then have a get and set method that retrieves the data from the dialog, or sets it on the dialog. To check for changed data, simply compare the original data to current data:
struct UserData {
QString name;
int age;
UserData(const QString & name_, int age_) :
name(name_), age(age_) {}
UserData() {}
};
class DialogBase : public QDialog {
QDialogButtonBox m_box;
protected:
QDialogButtonBox & buttonBox() { return m_box; }
virtual void isAccepted() {}
virtual void isApplied() {}
virtual void isReset() {}
virtual void isRejected() {}
public:
DialogBase(QWidget * parent = 0) : QDialog(parent) {
m_box.addButton(QDialogButtonBox::Apply);
m_box.addButton(QDialogButtonBox::Reset);
m_box.addButton(QDialogButtonBox::Cancel);
m_box.addButton(QDialogButtonBox::Ok);
connect(&m_box, SIGNAL(accepted()), SLOT(accept()));
connect(&m_box, SIGNAL(rejected()), SLOT(reject()));
connect(this, &QDialog::accepted, []{ isAccepted(); });
connect(this, &QDialog::rejected, []{ isRejected(); });
connect(&buttonBox(), &QDialogButtonBox::clicked, [this](QAbstractButton* btn){
if (m_box.buttonRole(btn) == QDialogButtonBox::ApplyRole)
isApplied();
else if (m_box.buttonRole(btn) == QDialogButtonBox::ResetRole)
isReset();
});
}
}
class UserDialog : public DialogBase {
QFormLayout m_layout;
QLineEdit m_name;
QSpinBox m_age;
UserData m_initialData;
public:
UserDialog(QWidget * parent = 0) : QDialog(parent), m_layout(this) {
m_layout.addRow("Name", &m_name);
m_layout.addRow("Age", &m_age);
m_age.setRange(0, 200);
m_layout.addRow(&buttonBox());
}
/// Used by external objects to be notified that the settings
/// have changed and should be immediately put in effect.
/// This signal is emitted when the data was changed.
Q_SIGNAL void applied(UserData const &);
UserData get() const {
return UserData(
m_name.text(), m_age.value());
}
void set(const UserData & data) {
m_name.setText(data.name);
m_age.setValue(data.age);
}
void setInitial(const UserData & data) { m_initialData = data; }
bool isModified() const { return get() == m_initialData; }
protected:
void isAccepted() Q_DECL_OVERRIDE { emit applied(get()); }
void isApplied() Q_DECL_OVERRIDE { emit applied(get()); }
void isReset() Q_DECL_OVERRIDE { set(m_initialData); }
};
If you're only checking whether the input fields are filled when the Dialog closes, you don't need the flags you can only check if there is any input.
If you are filling the input fields programatically at some points but are also only interested in the change when the dialog closes, you can also check in the close function whether the current input is equal to the one you set earlier.
From the code you posted, I can't really see what you need the flags for.
I'm displaying some information to the user in QScrollArea.
The user should have seen all contents, before she can proceed (at least the content should have been scrolled to the end)
How could I detect this in an easily?
Is the reimplementing of virtual void scrollContentsBy (int dx,int dy) the only way?
EDIT
I was able to solve it, but had to use some workarounds:
Scroll-action value sent by the signal actionTriggered(int) had never the value QAbstractSlider::SliderToMaximum (Qt4.8, Windows 7). So I've checked manually, if the slider value is close to maximum.
Even if scroll-bar widget was dragged by mouse till the bottom, the value of the scroll-bar is never the maximum. Only if the scroll-bar widget is moved by any other event such as arrow-down or mouse wheel, the value may become maximum. I've work-arounded it with recheckPosition()
I hope, there are better solutions.
void NegativeConfirmation::recheckPosition()
{
processScrollAction(1);
}
void NegativeConfirmation::processScrollAction( int evt)
{
if ( evt == QAbstractSlider::SliderToMaximum) // Have not managed to receive this action
{
ui->bConfirm->setEnabled(true);
}
//Another approach
QWidget * sw = ui->scrollArea->widget();
if ( sw ) //any content at all ?
{
QScrollBar * sb = ui->scrollArea->verticalScrollBar();
if ( sb )
{
int sbv = sb->value();
int sbm = sb->maximum()-10;
if ( sbm>0 && sbv >= sbm )
{
ui->bConfirm->setEnabled(true);
}
else
{
QTimer::singleShot(1000, this, SLOT(recheckPosition()));
}
}
}
}
QScrollArea inherits QAbstractSlider which provides this signal: -
void QAbstractSlider::actionTriggered(int action)
Where action can be QAbstractSlider::SliderToMaximum.
I expect you can connect to the this signal and test when the action is QAbstractSlider::SliderToMaximum, representing that the user has scrolled to the bottom.
How to make a QT dialog read-only? Any general way to implement it easily? For example
(1) set all its containing widgets disable. (how to implement it?)
(2) Intercept edit events like key pressed, mouse pressed but how not to intercept the one to close the dialog?
I think this feature should be very helpful.
Disabling the widgets can be done similar to the following:
void myDialog::disableWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();
foreach(QWidget* widget, widgets)
{
widget->setEnabled(false);
}
}
To intercept events, QDialog includes the function installEventFilter(QObject*).
This allows you to use a separate object to receive all events passed to the dialog. You can then choose to handle the event in the object, or pass it on to the dialog itself by calling the base class QObject::eventFilter
class MyEventHandler : public QObject
{
Q_OBJECT
protected:
bool MyEventHandler::eventFilter(QObject *obj, QEvent *event)
{
// handle key press events
if (event->type() == QEvent::KeyPress)
{
// Do something
// ...
return true; // event handled by the class
}
else
{ // ignore this event and pass it to the dialog as usual
return QObject::eventFilter(obj, event);
}
}
return false;
};
QDialog* dlg = new QDialog;
MyEventHandler evtHandler = new MyEventHandler;
dlg->installEventFilter(evtHandler);
Read-only is a strange term to apply to a dialog. Disabling all widgets as above does the trick. If you only wanted to make the input part of a QInputDialog read-only (while leaving scrollbars, buttons, etc. enabled), you could adapt that code as below:
QInputDialog dialog(this);
dialog.setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog.setWindowTitle("Title");
dialog.setLabelText("Label");
dialog.setTextValue("1\n2\n3\n");
QList<QWidget *> widgets = dialog.findChildren<QWidget *>();
foreach(QWidget* widget, widgets) {
if (strcmp(widget->metaObject()->className(),"QPlainTextEdit")==0) {
QPlainTextEdit *t = static_cast<QPlainTextEdit*>(widget);
t->setReadOnly(true);
}
}
dialog.exec();
I'm writing a qt-based c++ application. I have a number of buttons that I want to be mutually exclusive - only one can be toggled at a time. I generally use a QButtonGroup for this - it provides a nice logical way to manage sets of buttons. When one gets pressed, the previously-pressed one gets unpressed, which is exactly the behavior I want.
This time, however, I'd like to allow for the group to be entirely unchecked. Unfortunately this seems to be disallowed by QButtonGroup:
exclusive : bool
This property holds whether the button group is exclusive.
If this property is true then only one button in the group can be
checked at any given time. The user can click on any button to check
it, and that button will replace the existing one as the checked
button in the group.
In an exclusive group, the user cannot uncheck the currently checked
button by clicking on it; instead, another button in the group must be
clicked to set the new checked button for that group.
There are a number of ways to work around this, of course. I'm wondering if there's a pre-made alternative to QButtonGroup that allows this behavior, so that 1) I'm not reinventing the wheel and 2) I can stay within idiomatic qt to make project management easier in the future.
Any suggestions?
In Qt5, I use a similar solution as Laurent Michel's, but using the release event instead of the press event:
// Allow to uncheck button in exclusive group
void CustomButton::mouseReleaseEvent(QMouseEvent* a_Event) {
if(group()->checkedId()==group()->id(this)) {
if(isDown()) group()->setExclusive(false);
}
QToolButton::mouseReleaseEvent(a_Event);
group()->setExclusive(true);
}
For the sake of completeness, I would like to publish here one possible solution to the problem, as I just solved it in my case. Just beware that the following code is valid for Qt3. It may as well work for Qt4 and Qt5, because it doesn't use a lot of stuff.
So, I assume that I have a widget CustomWidget somewhere that contains buttons (of type CustomButton) and that one and only one button can be switched on. If one clicks another button in the widget, then the currently switched on button is switched off and the newly clicked button is switched on.
The CustomButtons contained in the CustomWidget are all contained in a QButtonGroup in the following way:
QButtonGroup* m_ButtonGroup = new QButtonGroup(this);
m_ButtonGroup->hide();
m_ButtonGroup->insert(Btn1);
m_ButtonGroup->insert(Btn2);
m_ButtonGroup->insert(Btn3);
m_ButtonGroup->setExclusive(true);
Here, Btn1, Btn2, and Btn3 are of type CustomButton
class CustomButton : public QToolButton
{
Q_OBJECT
public:
CustomButton (QWidget* apo_parent = 0, const char* as_name = 0);
virtual ~CustomButton ();
protected:
virtual void mousePressEvent(QMouseEvent* a_Event);
};
The method you want to implement specially is mousePressEvent. If its body is implemented in the following way:
void CustomButton ::mousePressEvent(QMouseEvent* a_Event)
{
if(group() && isToggleButton())
{
CustomButton* selectedButton(dynamic_cast<CustomButton*>(group()->selected()));
if(selectedButton)
{
if(selectedButton->name() == name())
{
group()->setExclusive(false);
toggle();
group()->setExclusive(true);
return;
}
}
}
QToolButton::mousePressEvent(a_Event);
}
then the widget behaves as you want.
Another similar solution as the previous answers, but using nextCheckState() which appears to be the more natural extension point to me:
void MyButton::setSemiExclusive(bool value)
{
mSemiExclusive = value;
}
void MyButton::nextCheckState()
{
if (mSemiExclusive)
{
if (auto g = group())
{
auto old = g->exclusive();
if (g->checkedButton() != this)
g->setExclusive(true);
QAbstractButton::nextCheckState();
g->setExclusive(old);
return;
}
}
QAbstractButton::nextCheckState();
}
This depends on the associated QButtonGroup having set exclusive to false.
If you don't want to extend the button class, you can also accomplish this by using signals (Qt5, Python):
from PySide import QtGui
class View(QtGui.QWidget):
def __init__(self):
self.buttonGroup = QtGui.QButtonGroup(self)
for button in buttons:
self.buttonGroup.addButton(button)
button.pressed.connect(buttonPressed)
button.released.connect(buttonReleased)
def buttonPressed(self):
button = self.sender()
checkedButton = self.buttonGroup.checkedButton()
if checkedButton != None and checkedButton.objectName() == button.objectName():
self.buttonGroup.setExclusive(False)
def buttonReleased(self):
button = self.sender()
if self.buttonGroup.exclusive() == False:
button.setChecked(False)
self.buttonGroup.setExclusive(True)
def manualDeselection:
self.buttonGroup.setExclusive(False)
self.buttonGroup.checkedButton().setChecked(False)
self.buttonGroup.setExclusive(True)
My solution is to derive the QButtonGroup, set it to non exclusive internaly and manage the states by yourself
class myQButtonGroup : public QButtonGroup
{
Q_OBJECT
public:
explicit myQButtonGroup(QObject *parent = Q_NULLPTR) : QButtonGroup(parent) {
_bExclusive = true;
QButtonGroup::setExclusive(false);
connect(this, SIGNAL(buttonClicked(QAbstractButton *)), SLOT(buttonClicked(QAbstractButton *)));
}
void setExclusive(bool bExclusive) { _bExclusive = bExclusive; }
bool exclusive() const { return _bExclusive; }
protected slots:
void buttonClicked(QAbstractButton *button) {
if (_bExclusive) {
// just uncheck all other buttons regardless of the state of the clicked button
QList<QAbstractButton *> buttonlist = buttons();
for (auto iBtn = buttonlist.begin(); iBtn != buttonlist.end(); ++iBtn) {
QAbstractButton *pBtn = *iBtn;
if (pBtn && pBtn != button && pBtn->isCheckable()) pBtn->setChecked(false);
}
}
}
protected:
bool _bExclusive;
};