I am creating some automated GUI tests in my application using QTest.
I can access the widgets from my application using the command:
savePushButton = mainWindow->findChild<QPushButton *>("savePushButton");
It is working fine, but now I have to click on the OK button of a QMessageBox.
I created the QMessageBox in my application like this:
if( something_wrong )
{
QMessageBox::warning(new Widget(), "Title", "Something wrong!");
}
How can I have access to this QMessageBox, and its buttons?
I found a solution on the following link: http://www.qtcentre.org/threads/31239-Testing-modal-dialogs-with-QTestLib .
It uses the command QApplication::topLevelWidgets(); to get a widget list. Then it searches for the message box widget and simulates a key enter (QTest::keyClick(mb, Qt::Key_Enter);) which closes the message box.
Example:
void MyTest::testDialog()
{
QTimer::singleShot(500, this, SLOT(timeOut()));
QVERIFY(functionThatProducesMessageBox());
}
void MyTest::timeOut()
{
QWidgetList allToplevelWidgets = QApplication::topLevelWidgets();
foreach (QWidget *w, allToplevelWidgets) {
if (w->inherits("QMessageBox")) {
QMessageBox *mb = qobject_cast<QMessageBox *>(w);
QTest::keyClick(mb, Qt::Key_Enter);
}
}
}
The header file must contain the Q_OBJECT macro to use the signals and slots mechanism.
Example:
class MyClass: public QWidget
{
Q_OBJECT
public:
...
It worked well for me since the UI (thread) is blocked when the message box appears.
Note: remember to rebuild the project when you add the Q_OBJECT macro.
It often helps to look to Qt's auto tests:
void ExecCloseHelper::timerEvent(QTimerEvent *te)
{
if (te->timerId() != m_timerId)
return;
QWidget *modalWidget = QApplication::activeModalWidget();
if (!m_testCandidate && modalWidget)
m_testCandidate = modalWidget;
if (m_testCandidate && m_testCandidate == modalWidget) {
if (m_key == CloseWindow) {
m_testCandidate->close();
} else {
QKeyEvent *ke = new QKeyEvent(QEvent::KeyPress, m_key, Qt::NoModifier);
QCoreApplication::postEvent(m_testCandidate, ke);
}
m_testCandidate = Q_NULLPTR;
killTimer(m_timerId);
m_timerId = m_key = 0;
}
}
Judging from that code, you can get the message box via QApplication::activeModalWidget(). Testing native (I'm assuming they're native) widgets is difficult, which is likely why they chose to send key events, as you don't need to know e.g. the location of the buttons for those, as you would with a mouse click.
Related
I've made an app with two forms.
When I press the save button in the second form, it updates the DB Record, and returns back to the first form. I've connected the two forms via Signal-Slot with this code:
DruckerData.h
signals:
void btnSavePressed(QString printerName);
DruckerData.cpp
UiMainWindow frmMain;
connect(this,SIGNAL(btnSavePressed(QString)),&frmMain,SLOT(refreshSaved( QString )));
emit btnSavePressed(ui->ledit_druckerName->text());
this->hide();
UiMainWindow.h
public slots:
void refreshSaved(QString printerName);
UiMainWindow.cpp
void UiMainWindow::refreshSaved(QString printerName){
qDebug()<<"Updated: "<<printerName;
show_list(); //<<<<<<<<<<<<<<<<<<<<<< this function
}
show_list
void UiMainWindow::show_list (){
QList<DB_Printers_lvs> list;
DB_Printers_lvsTransporter t("LVS");
QString wc;
this->setCursor(Qt::WaitCursor);
wc = QString("where 1=1 order by nam_printer");
if (!t.load_dbPrinters_lvs_wc(&list,wc))
{
log()<< "get printers failed"<< wc << t.getLastError();
this->setCursor(Qt::ArrowCursor);
return;
}
ui.treeWidget->clear();
foreach (DB_Printers_lvs db, list)
{
QTreeWidgetItem *item = new QTreeWidgetItem(0);
printer_to_qtreewidgetitem(item, db);
ui.treeWidget->insertTopLevelItem(ui.treeWidget->topLevelItemCount(), item);
}
ui.treeWidget->header()->resizeSections(QHeaderView::ResizeToContents);
ui.bow_search->apply();
this->setCursor(Qt::ArrowCursor);
}
When I press the button on the second form and the first form shows I see debug writing Updated with printer name but the problem is how can I call or start this funktion show_list()?
Thanks for help.
The problem that you create second instance of UiMainWindow here:
UiMainWindow frmMain;
Then you connect signal with this second instance, call it's slots, but you don't even show this second instance of MainForm. Instead of this, you should connect signal and slot inside the UiMainWindow just after you create DruckerData form. Unfortunatly there is no this code at your question so i can't show exactly place. This should be something like this:
//Inside UiMainWindow
DruckerData *data = new DruckerData(this);
connect(data, SIGNAL(btnSavePressed(QString)),this,SLOT(refreshSaved( QString )));
data->show();
TabletEvents comes as mouse events.
Actual for MAC OS Qt 4.8.0 - 4.8.5.
Works fine in Qt 4.7.3 on any OS and Qt 4.8.0 on Windows and Linux.
I have two instances of QGraphcisScene and two instances of QGraphicsView.
The same types, but one view have a parent, and the another - doesn't (also it's transparent, used for drawing something over desktop).
I'm using tablet (wacom pen and touch) for painting. I handle QTabletEvents and it works only for QGrahicsView instance that doesn't have parent (means parent==0).
On the view with parent (
QMainWindow->centralWidget->ControlContainerWidget->QStackedLayout->QGraphicsView
) tablet events doesn't comes. They comes to QApplication::eventFilter fine, but doesn't comes to view. They comes to QMainWindow as mouseEvents.
If i set parent to 0, tablet events delivers fine.
The 1st receiver of tablet event is QMainWindow.
I see that inside qt_mac_handleTabletEvent:
QWidget *qwidget = [theView qt_qwidget];
QWidget *widgetToGetMouse = qwidget;
And then:
`qt_sendSpontaneousEvent(widgetToGetMouse, &qtabletEvent);`
qtabletEvent - is not accepted event created just before calling sendSpontaneousEvent.
Then inside QApplication::notify():
QWidget *w = static_cast<QWidget *>(receiver);
QTabletEvent *tablet = static_cast<QTabletEvent*>(e);
QPoint relpos = tablet->pos();
bool eventAccepted = tablet->isAccepted();
while (w) {
QTabletEvent te(tablet->type(), relpos, tablet->globalPos(),
tablet->hiResGlobalPos(), tablet->device(), tablet->pointerType(),
tablet->pressure(), tablet->xTilt(), tablet->yTilt(),
tablet->tangentialPressure(), tablet->rotation(), tablet->z(),
tablet->modifiers(), tablet->uniqueId());
te.spont = e->spontaneous();
res = d->notify_helper(w, w == receiver ? tablet : &te);
eventAccepted = ((w == receiver) ? tablet : &te)->isAccepted();
e->spont = false;
if ((res && eventAccepted)
|| w->isWindow()
|| w->testAttribute(Qt::WA_NoMousePropagation))
break;
relpos += w->pos();
w = w->parentWidget();
}
tablet->setAccepted(eventAccepted);
As we can see:
res = d->notify_helper(w, w == receiver ? tablet : &te);
It calls event processing by filters, layouts and then - QMainWindow::tabletEvent. Default implementation is event->ignore().
Since QMainWindow have no Parent, it is all.
So tablet event doesn't comes to QMainWindow childs.
Then seems it is QWidget *qwidget = [theView qt_qwidget]; works wrong.
Unfortunately, i can't debug it...
Please give me some hints... i'm stucked...
I spent more time on comparison Qt 4.8.0 and 4.7.3 and now i see that it is the problem in internal qt event dispatcher. It sends event to NSWindow (QMainWindow) instead of NSView (QGraphicsView).
I didn't found where is the problem, but i found that QMainWindow returns false from ::event() method.
So i reimplemented that method and parsed tablet event there:
bool UBMainWindow::event(QEvent *event)
{
bool bRes = QMainWindow::event(event);
if (NULL != UBApplication::boardController)
{
UBBoardView *controlV = UBApplication::boardController->controlView();
if (controlV && controlV->isVisible())
{
switch (event->type())
{
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
{
return controlV->directTabletEvent(event);
}
}
}
}
return bRes;
}
The problem is: i need to use tablet for any controls in application, so i need to determine when QGraphicsView is under mouse cursor:
bool UBBoardView::directTabletEvent(QEvent *event)
{
QTabletEvent *tEvent = static_cast<QTabletEvent *>(event);
tEvent = new QTabletEvent(tEvent->type()
, mapFromGlobal(tEvent->pos())
, tEvent->globalPos()
, tEvent->hiResGlobalPos()
, tEvent->device()
, tEvent->pointerType()
, tEvent->pressure()
, tEvent->xTilt()
, tEvent->yTilt()
, tEvent->tangentialPressure()
, tEvent->rotation()
, tEvent->z()
, tEvent->modifiers()
, tEvent->uniqueId());
if (geometry().contains(tEvent->pos()))
{
if (NULL == widgetForTabletEvent(this->parentWidget(), tEvent->pos()))
{
tabletEvent(tEvent);
return true;
}
}
return false;
}
Also i need to stop handle tablet events for QGraphicsView childs.
QWidget *UBBoardView::widgetForTabletEvent(QWidget *w, const QPoint &pos)
{
Q_ASSERT(w);
UBBoardView *board = qobject_cast<UBBoardView *>(w);
QWidget *childAtPos = NULL;
QList<QObject *> childs = w->children();
foreach(QObject *child, childs)
{
QWidget *childWidget = qobject_cast<QWidget *>(child);
if (childWidget)
{
if (childWidget->isVisible() && childWidget->geometry().contains(pos))
{
QWidget *lastChild = widgetForTabletEvent(childWidget, pos);
if (board && board->viewport() == lastChild)
continue;
if (NULL != lastChild)
childAtPos = lastChild;
else
childAtPos = childWidget;
break;
}
else
childAtPos = NULL;
}
}
return childAtPos;
}
Maybe when i will have more time - i will investigate qt more deeply and fix that problem at all.
In QT, a created lineEdit shows a text using the setText() method.
But the cursor is movable for the default text. I want the cursor should not be movable for the default text.
My lineEdit type has been set as password. Hence the default text('Password') is also displayed as '********'. Whenever user types the type has to be changed as password and when there is no text or until the user have not typed any text, the lineEdit should display the plain text 'password'
Any idea to fix the above two issues?
In the constructor put
ui->lineEdit->setPlaceholderText("password");
ui->lineEdit->setReadOnly(1);
And in on_lineEdit_selectionChanged() SLOT, put
ui->lineEdit->setText("");
ui->lineEdit->setEchoMode(QLineEdit::Password);
ui->lineEdit->setReadOnly(0);
I noticed this question has tag pyqt so I'll put an actual answer related to that tag for those actually looking for a python way instead of c++.
self.searchEditText = QtGui.QLineEdit()
self.searchEditText.setPlaceholderText("Search for word")
I managed to do what you want by deriving a class from QLineEdit as per following..
Constructor..
QCustomLineEdit::QCustomLineEdit(QWidget *parent) :
QLineEdit(parent)
{
connect(this, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int)));
setEchoMode(QLineEdit::Password); // Echo mode in your case..
m_echoMode = echoMode(); // Member variable to store original echo mode..
m_placeHolderText = "Password"; // Member variable..
m_isPlaceHolderActive = true; // Member varible..
// Default case..
setPlaceholderText("");
setStyleSheet("QCustomLineEdit{color: gray;}");
setEchoMode(QLineEdit::Normal);
setText(__placeHolderText);
}
Override keyPressEvent..
void QCustomLineEdit::keyPressEvent(QKeyEvent *e)
{
if(m_isPlaceHolderActive)
{
if(e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)
e->accept();
else
QLineEdit::keyPressEvent(e);
return;
}
QLineEdit::keyPressEvent(e);
}
Cursor position change event..
void QCustomLineEdit::onCursorPositionChanged(int /*oldPos*/, int newPos)
{
if(m_isPlaceHolderActive)
{
if(newPos != 0)
setCursorPosition(0);
}
}
Text change event..
void QCustomLineEdit::onTextChanged(const QString &text)
{
if(m_isPlaceHolderActive)
{
if(text.compare(m_placeHolderText) != 0)
{
m_isPlaceHolderActive = false;
// Remove the 'placeHolderText' from 'text' itself..
QString temp = text;
temp = temp.mid(0, text.lastIndexOf(m_placeHolderText));
setStyleSheet("QCustomLineEdit{color: black;}");
setEchoMode(m_echoMode);
setText(temp);
}
else
{
setEchoMode(QLineEdit::Normal);
setText(m_placeHolderText);
setStyleSheet("QCustomLineEdit{color: gray;}");
setCursorPosition(0);
}
}
else
{
if(text.isEmpty())
{
m_isPlaceHolderActive = true;
setStyleSheet("QCustomLineEdit{color: gray;}");
setEchoMode(QLineEdit::Normal);
setText(m_placeHolderText);
}
}
}
I have written it very hastily to just show you. Test it yourself and feel free to point any mistake(s) or optimization(s). Hope this helps.
For question 1, in Qt 5.0 and higher, setPlaceholderText does what you want. https://codereview.qt-project.org/#change,45326
I try to load to plugins. If they are loaded with success , then i must connect returned widgets.
With one plugin i create an action and add it to a menu , with another plugin i create a label and add it to window. Even if i get this error during runtime (when app loads plugins) , these two widgets are created and are visible. But there is no connection between them.
This is how i try to connect widgets
QObject *plugin = pluginLoader.instance();
if (plugin) {
myAction = qobject_cast<ActionInterface *>(plugin);
if (myAction) {
pluginMenu->addAction(myAction->newAction());
verify ++;
}
myLabel = qobject_cast<LabelInterface *>(plugin);
if (myLabel) {
layout->addWidget(myLabel->newLabel());
verify++;
}
if (verify == 2)
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Error message is :
QObject::connect: Cannot connect (null)::pushMyAction() to LabelPlugin::setTextforLabel()
You have two different plugins. Apparently one can be cast to an ActionInterface but not a LabelInterface, and the other can be cast to a LabelInterface but not an ActionInterface.
Your idea here seems to be that once you have both plugins loaded (and a verify count of 2) then it's safe to make a connect call between these plugins. However you appear to be trying to cast the second loaded plugin to serve as both the signal and the slot. This is because each time you run the code you overwrite both myAction and myLabel. So at minimum:
QObject* plugin = pluginLoader.instance();
if (plugin) {
ActionInterface* myActionTemp = qobject_cast<ActionInterface*>(plugin);
if (myActionTemp) {
myAction = myActionTemp;
pluginMenu->addAction(myAction->newAction());
verify++;
}
LabelInterface* myLabelTemp = qobject_cast<LabelInterface*>(plugin);
if (myLabelTemp) {
myLabel = myLabelTemp;
layout->addWidget(myLabel->newLabel());
verify++;
}
/* if (myAction and myLabel) would be less convoluted... */
if (verify == 2) {
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Still, this looks like a fairly brittle design that could use some rethinking...!
QObject *plugin = pluginLoader.instance();
if (plugin) {
if (plugin->inherits("ActionInterface")) {
myAction = qobject_cast<ActionInterface *>(plugin);
pluginMenu->addAction(myAction->newAction());
}
if (plugin->inherits("LabelInterface")) {
myLabel = qobject_cast<LabelInterface *>(plugin);
layout->addWidget(myLabel->newLabel());
}
if (myLabel && myAction)
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Remove this "ugly" verify counter. Remember to initialize myLabel and myAction with NULL
I am trying to write an application, but it is constantly crashing when using the uiimagepickercontroller. I thought that it might be because I was not disposing of the picker after each use, but it will often freeze up on first run as well. Usually I'll take a picture and it just freezes, never asking to "use" the picture.
Do you have any suggestions? Here is my code. Has anyone gotten this to work?
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
myPicker = new UIImagePickerController();
myPicker.Delegate = new myPickerDelegate(this);
myAlbumButton.Clicked += delegate {
if(UIImagePickerController.IsSourceTypeAvailable(UIImagePickerControllerSourceType.PhotoLibrary)){
myPicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
myPicker.AllowsEditing = true;
this.PresentModalViewController (myPicker, true);
}else{
Console.WriteLine("cannot get album");
}
};
myCameraButton.Clicked += delegate {
if(UIImagePickerController.IsSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
myPicker.SourceType = UIImagePickerControllerSourceType.Camera;
//myPicker.AllowsEditing = true;
this.PresentModalViewController (myPicker, true);
}else{
Console.WriteLine("cannot get camera");
}
};
}
private class myPickerDelegate : UIImagePickerControllerDelegate
{
private TestView _vc;
public myPickerDelegate ( TestView controller):base()
{
_vc = controller;
}
public override void FinishedPickingImage (UIImagePickerController myPicker, UIImage image, NSDictionary editingInfo)
{
// TODO: Implement - see: http://go-mono.com/docs/index.aspx?link=T%3aMonoTouch.Foundation.ModelAttribute
_vc.myImageView.Image = image;
myPicker.DismissModalViewControllerAnimated(true);
}
}
Try to call your event handlers code from the main thread by using BeginInvokeOnMainThread().
So my issue was very similar.
Instead of having a delegate class, I had the delegates inline for the picker.
For some reason the app froze every time after talking the image, not stopping in any breakpoint after that.
The solution that worked for me was to use this book:
http://www.scribd.com/doc/33770921/Professional-iPhone-Programming-with-MonoTouch-and-NET-C