PDA

View Full Version : connect to PushButton in another class: object missing in reference



homerun4711
29th December 2010, 18:40
Hello!

I want to connect a function in my MainWindow-class to a PushButton in a QDialog, that could be opened from the MainWindow.
The following message appears while compiling:


object missing in reference to ‘Ui_AddressBook::addCustomerButton’

MainWindow.cpp

connect(AddressBook::addCustomerButton ,SIGNAL(clicked()), this, SLOT(newcustomer()));

addressbook.h

class AddressBook : public QDialog, public Ui::AddressBook

ui_addressbook.h


class Ui_AddressBook
{
public:
QPushButton *addCustomerButton;

After typing "connect(AddressBook::" the code completion in QtCreator appears and shows the button.

Do you know what I am doing wrong?

Is it neccessary to connect to button to a defined object, e.g.


AddressBook* test = new AddressBook;
connect(test->pushButton, SIGNAL(clicked()),...


Or is it only possible to connect it to a class in general? Or both?

Kind regards,
HomeR

nroberts
29th December 2010, 18:47
Yeah, the syntax you attempted to use is wrong for what you're trying to do. That syntax resolves a member, it doesn't specify a member within an object. If you'd put a & before it you would have been creating a member pointer.

You need the instance->member syntax, and if there where many members of the same name within different levels of the lineage of that class you'd need to also use the resolution syntax you attempted, but in a different way: instance->AddressBook::addCustomerButton.

In this case though you can probably just use the variable within your MainWindow, assume "addressBook" and do this:


connect(addressBook->addCustomerButton, SIGNAL(clicked())....)

homerun4711
29th December 2010, 19:35
Ok, it works. Thank you for your answer and the explanation.

Is it common, that in such a case the connect-statement appears directly
after the creation of the object? E.g.


AddressBook *customerlist = new AddressBook();
connect(customerlist->addCustomerButton ,SIGNAL(clicked()), this, SLOT(newcustomer()));

I tried to connect the button in the constructor of MainWindow together with all the
filemenu-toolbar-action-stuff, but the QDialog "AddressBook* newcustomer" is not present at that time,
because it is opened by the user later. So I guess it its not possible to connect it before, in
case if there will be an exiting object, or am I wrong?

nroberts
29th December 2010, 19:49
Ok, it works. Thank you for your answer and the explanation.

Is it common, that in such a case the connect-statement appears directly
after the creation of the object? E.g.


AddressBook *customerlist = new AddressBook();
connect(customerlist->addCustomerButton ,SIGNAL(clicked()), this, SLOT(newcustomer()));

I tried to connect the button in the constructor of MainWindow together with all the
filemenu-toolbar-action-stuff, but the QDialog "AddressBook* newcustomer" is not present at that time,
because it is opened by the user later. So I guess it its not possible to connect it before, in
case if there will be an exiting object, or am I wrong?

You're not wrong and this isn't unique to Qt either. The way to deal with this cleanly is to have a utility function (probably private to your main window but could be external and/or anonymous local) that's sole responsibility is prepping a new AddressBook by creating one, attaching signals, and returning it.

tbscope
29th December 2010, 19:53
The connect function always requires an existing object.
It is a good idea to keep the connect statements as close as possible to the object creation. Or to put it in a different way, always make sure that when you create a connection, both the sender and receiver objects exist.

homerun4711
29th December 2010, 21:01
Ok, I will try this. But how to attach a signal to a not yet existing object, in my case the AddressBook, which is not created if MainWindow is started?
Creating the connection in AddressBook's constructor to send a signal from Addressbook to its parent MainWindow?
Or can one attach a signal to a not yet existing object, which is connected or available if the object exists?

tbscope
29th December 2010, 21:11
No, you create the connection after you've created the AddressBook object.
You can have multiple AddressBook objects, all connected to different slots or signals.
You can't use class definitions because that would be ambiguous. The meta object compiler will not know what you want.

So, at the point in your application, when you create a new AddressBook object, just after the creation of the object, create the connection.
This of course also means that if the receiver or sender is the main window, the main window object should exist too.

Example:
MyMainWindow is the main window class.
The openAddressBook() function creates a new AddressBook object.


void MyMainWindow::openAddressBook()
{
AddressBook *book = new AddressBook;
connect(this, SIGNAL(search(QString)), book, SLOT(search(QString)));
}

The above example is only to demonstrate the position of the connect function in relation to the creation of the AddressBook object. The AddressBook object goes out of scope which isn't a good idea, better is to make it a member variable.

nroberts
29th December 2010, 21:17
Ok, I will try this. But how to attach a signal to a not yet existing object, in my case the AddressBook, which is not created if MainWindow is started?
Creating the connection in AddressBook's constructor to send a signal from Addressbook to its parent MainWindow?


You could do that if you want. You would need to ensure that AddressBook is always provided with your particular main window. That of course is a great reason not to do it that way since it would make your AddressBook widget very difficult to use elsewhere.



Or can one attach a signal to a not yet existing object, which is connected or available if the object exists?

Nope. I still recommend a utility function that builds and connects the AddressBook the way you want it connected.

homerun4711
29th December 2010, 23:24
The AddressBook object goes out of scope which isn't a good idea, better is to make it a member variable.

I don't completly understand what you mean with that, could you please explain it with a few words?

wysota
29th December 2010, 23:29
I want to connect a function in my MainWindow-class to a PushButton in a QDialog, that could be opened from the MainWindow

To cut to the chase... What I will propose is not directly related to your problem but indirectly it does solve it. I'd suggest improving your design -- you currently publically inherit from the UI object. The "proper" design is to inherit it privately to hide all the implementation details (such as class members) from outside world and instead only access them through getters and setters. This of course makes the following impossible:

connect(customerlist->addCustomerButton ,SIGNAL(clicked()), this, SLOT(newcustomer()));
... as you don't have access to "addCustomerButton". Instead your dialog should have a signal of its own that is emitted when the addCustomerButton is clicked. Then you connect to that signal instead of the original signal and should the internals of your address book class change, you don't have to modify any code other than the address book itself. Furthermore it might happen that you add some more details to the address book together with some other means to trigger adding a new customer. With your current design you'd have to make another signal/slot connection, etc. With what I suggest, you'd just emit the dialog's signal again.


class AddressBook : public QDialog, private Ui::AddressBook {
Q_OBJECT
public:
AddressBook(QWidget *parent = 0) : QDialog(parent) {
setupUi(this);
connect(addCustomerButton, SIGNAL(clicked()), this, SIGNAL(newCustomerRequested())); // this causes the latter to be emitted whenever the former is emitted
}
signals:
void newCustomerRequested();
};

// ...
connect(customerlist, SIGNAL(newCustomerRequested()), this, SLOT(newcustomer()));

This all requires a bit extra coding but greatly improves the overall design. And, as I said, indirectly solves the original problem.

Better yet newCustomer() should probably be a slot in the dialog and not in your other class.

nroberts
30th December 2010, 00:09
I don't completly understand what you mean with that, could you please explain it with a few words?

You quoted tbscope as saying, "The AddressBook object goes out of scope which isn't a good idea, better is to make it a member variable."

They're talking about their function written above that statement:

void MyMainWindow::openAddressBook()
{
AddressBook *book = new AddressBook;
connect(this, SIGNAL(search(QString)), book, SLOT(search(QString)));
}

It creates a memory leak because the book variable doesn't survive beyond the call to openAddressBook(). Since it was allocated on the free-store (new) it's still sitting out there, unreachable, taking up space.

Qt has a set of ownership rules though that can be used instead of making it a member variable. You can tie the existence of that address book to the lifetime of another QObject by making that object the address book's owner (supplying it as the object constructor parameter).

If you need to get access to it again later though, to call functions on it or whatever, you won't be able to. Whether or not you actually need to do this is another story. A lot of times in signal driven code you just need to have access to objects long enough to connect them together and then you can not care anymore. What, exactly, you need is hard to tell at this point though.

If you didn't understand what I just said....this one is important. You'll want to read up on C++ scoping rules, memory allocation, and Qt's specific "ownership" (another thing to look up) semantics.

Added after 17 minutes:

Wysota's redesign of the problem is better but I'd hate to see the OP think it was better enough. One of the most frustrating things in a dialog driven interaction is when you hit 'cancel' and you're not back where you started.

Wysota does mention that newCustomer should be in the dialog but not why. The reason why is that dialogs are often, and it appears that probably in the OP's case, generators of operations as a whole. In this case that operation might be "create/edit address book". Because we want the user to be able to cancel the operation it is not sufficient to have a button "new customer" in the dialog that creates the new customer in the main program's database. We want the dialog to track all the changes made and then IFF the user presses OK the main program either queries the dialog for information to perform the whole operation or the dialog spits up a signal that the main application is listening too.

Often the easiest way to accomplish this is to simply send the dialog a copy of the program's database, let it alter it any way it sees fit, and then replace the original if the user presses OK. Of course, this is for small/trivial "databases" but discussing anything more complex is really beyond "newbie" level.

In short, consider your dialog an application of its very own that takes some input...does stuff to it, and then generates output. This is nearly always the way you want your dialog to work in order to give the user what they expect. You could even simplify things for yourself if you just forget all about the main window for now and make a Qt application that simply preps your dialog, shows it, and then prints out the results or something. Starting simply and getting the dialog working FIRST since it should already be capable of existing independently of the rest of your application (minus the data it manipulates of course).

homerun4711
30th December 2010, 08:47
Thanks for your good suggestions and explanations.

So far, my QDialog is nearly standalone, but I guess this could be improved.
Well, so much to learn about clean and tidy OOP... :)

wysota
30th December 2010, 10:37
One of the most frustrating things in a dialog driven interaction is when you hit 'cancel' and you're not back where you started.
That's indeed a pain in the neck, many applications cancel some operations and leave others. When working with databases there is an easy way to overcome this with two simple lines of code regardless where the code is executed. It's enough to put the whole dialog in a database transaction. Then after cancelling the dialog you simply rollback the transaction and have the exact state from before the dialog was executed. When not working with databases you can have a similar result if your application is command based (e.g. using QUndoCommand) - then you simply undo all commands performed by the dialog.