Qt provides the signals and slots mechanism for communicating between widgets. Signals are emitted by widgets when particular events occur. We can connect signals to slots, either pre-defined slots or those we create ourselves. In older toolkits this communication would be achieved using callbacks. (For a full explanation of Qt's signals and slots mechanism see the on-line Signals and Slots documentation.)
Some of an application's functionality can be obtained simply by connecting pre-defined signals and slots. In multiclip there is only one pre-defined connection that we can use, but in the richedit application that we'll build in Chapter 2 "Creating Main Windows with Actions, Toolbars & Menus" we will use many pre-defined signals and slots to get a lot of functionality without having to write any code.
We will connect the Quit button's clicked() signal to the form's accept() slot. The accept() slot notifies the dialog's caller that the dialog is no longer required; since our dialog is our main window this will close the application. Preview the form (press Ctrl+T); click the Quit button. The button works visually but does nothing. Press Esc or close the preview window to leave the preview.
Click the Connect Signals/Slots toolbar button. Click the Quit button, drag to the form and release. The Edit Connections dialog will pop up. The top left hand list box lists the Signals that the widget we've clicked can emit. At the top right is a combobox which lists the form and its widgets; any of these are candidates for receiving signals. Since we released on the form rather than a widget the slots combobox shows the form's name, 'MulticlipForm'. Below the combobox is a list box which shows the slots available in the form or widget shown in the combobox. Note that only those slots that can be connected to the highlighted signal are shown. If you clicked a different signal, for example the toggled() signal, the list of available slots would change. Click the clicked() signal, then click the accept() slot. The connection will be shown in the Connections list box. Click OK.
We will make a great many signal/slot connections as we work through the examples, including connections to our own custom slots. Signal/slot connections (using pre-defined signals and slots) work in preview mode. Press Ctrl+T to preview the form; click the form's Quit button. The button now works correctly.
In the first version of Qt Designer you could create the signatures of your custom slots and make the connections, but you could not implement your slots directly. Instead you had to subclass the form and code your slots in the subclass. The subclassing approach is still available, and makes sense in some situations. But now you can implement your slots directly in Designer so for many straightforward dialogs and windows subclassing is no longer necessary.
The multiclip application requires four slots, one for each button, but only three need to be custom slots since we connected a signal to a pre-defined slot to make the Quit button functional. We need a slot for the Add Clipping button; this will add the current clipping to the list box. The Copy Previous button requires a slot which will copy the selected list box item to the current clipping line edit (and to the clipboard). The Delete Clipping button needs a slot to delete the current clipping and the current list box item. We will also need to write some initialization code so that when the application starts it will put the current clipboard text (if any) into the line edit. The code is written directly in Designer; the snippets are taken from the generated qt/tools/designer/eg/multiclip/multiclip.cpp file.
We'll need Qt's global clipboard object throughout the code which would mean calling QApplication::clipboard() or qApp->clipboard() in several places. Rather than perform all these function calls we'll keep a pointer to the clipboard in the form itself. Click the Source tab of the Object Explorer. (If the Object Explorer isn't visible click Window|Views|Object Explorer.) The Source tab shows us the functions in our form, the class variables, the forward declarations and the names of the include files we've asked for.
Right click the Class Variables item, then click New on the pop up menu. (If there had been any existing variables the pop up menu would also have a Delete option.) Type in 'QClipboard *cb;' and press Return. In the init() function we will assign this pointer to Qt's global clipboard object. We also need to declare the clipboard header file. Right click Includes (in Declaration), then click New. Type in '<qclipboard.h>' and press Return. Since we need to refer to the global application object, qApp, we need to add another include declaration. Right click Includes (in Implementation), then click New. Type in '<qapplication.h>' and press Return. The variable and declarations will be included in the code generated from Qt Designer's .ui file.
We will invoke Qt Designer's code editor and write the code.
We'll look at the init() function first. Qt Designer creates an empty init() into which we can add our own code.
void MulticlipForm::init() { lengthLCDNumber->setBackgroundColor( darkBlue ); currentLineEdit->setFocus(); cb = qApp->clipboard(); connect( cb, SIGNAL( dataChanged() ), SLOT( dataChanged() ) ); if ( cb->supportsSelection() ) connect( cb, SIGNAL( selectionChanged() ), SLOT( selectionChanged() ) ); dataChanged(); } |
Since we've referred to the dataChanged() and selectionChanged() slots we'll code them next, starting with dataChanged().
void MulticlipForm::dataChanged() { QString text; text = cb->text(); clippingChanged( text ); if ( autoCheckBox->isChecked() ) addClipping(); } |
The selectionChanged() slot is only applicable under the X Window System. Users of MS Windows can still include the code to ensure the application works cross-platform.
void MulticlipForm::selectionChanged() { cb->setSelectionMode( TRUE ); dataChanged(); cb->setSelectionMode( FALSE ); } |
In the dataChanged() slot we called another custom slot, clippingChanged().
void MulticlipForm::clippingChanged( const QString & clipping ) { currentLineEdit->setText( clipping ); lengthLCDNumber->display( (int)clipping.length() ); } |
The next slot we'll code will perform the Add Clipping function. This slot is called by our code internally (see the dataChanged() slot above), and when the user clicks the Add Clipping button. Since we want Qt Designer to be able to set up a connection to this slot instead of just typing it in the editor window we'll have Designer create its skeleton for us. Click Edit|Slots to invoke the Edit Slots dialog. Click New Slot and replace the default name of 'new_slot()' with 'addClipping()'. There is no need to change the access specifier or return type. Now that we've created our slot we can implement it in the code editor where it has now appeared.
The Add Clipping button is used to copy the clipping from the Current Clipping line edit into the list box. We also update the length number.
void MulticlipForm::addClipping() { QString text = currentLineEdit->text(); if ( ! text.isEmpty() ) { lengthLCDNumber->display( (int)text.length() ); int i = 0; for ( ; i < (int)clippingsListBox->count(); i++ ) { if ( clippingsListBox->text( i ) == text ) { i = -1; // Do not add duplicates break; } } if ( i != -1 ) clippingsListBox->insertItem( text, 0 ); } } |
To make the Add Clipping button functional we need to connect the button's clicked() signal to our addClipping() slot. Click the Connect Signals/Slots toolbar button. Click the Add Clipping button, drag to the form and release. (Make sure you drag to the form rather than another widget -- the form will have a thin pink border during the drag. If you make a mistake simply change the name in the Slots combobox.) The Edit Connections dialog will appear. Click the clicked() signal and our addClipping() slot. Click OK to confirm the connection.
The Copy Previous button is used to copy the selected clipping from the list box into the line edit. The clipping is also placed on the clipboard. The procedure is the same as for the Add Clipping button: first we create the slot, then we implement it and finally we connect to it:
Create the slot.
Click the Edit|Slots menu item to invoke the Edit Slots dialog. Click New Slot and replace the default 'new_slot()' name with 'copyPrevious()'. Click OK.
Implement the slot.
void MulticlipForm::copyPrevious() { if ( clippingsListBox->currentItem() != -1 ) { cb->setText( clippingsListBox->currentText() ); if ( cb->supportsSelection() ) { cb->setSelectionMode( TRUE ); cb->setText( clippingsListBox->currentText() ); cb->setSelectionMode( FALSE ); } } } |
Connect to the slot.
Click the Connect Signals/Slots toolbar button. Click the Copy Previous button, drag to the form and release. The Edit Connections dialog will pop up. Click the clicked() signal and the copyPrevious() slot. Click OK.
We take the same approach to the Delete Clipping button.
Click Edit|Slots to invoke the Edit Slots dialog. Click New Slot and replace the default name with 'deleteClipping()'. Click OK.
The Delete button must delete the current item in the list box and clear the line edit.
void MulticlipForm::deleteClipping() { clippingChanged( "" ); clippingsListBox->removeItem( clippingsListBox->currentItem() ); } |
Connect the Delete Clipping button's clicked() signal to our deleteClipping() slot. (Press F3 -- which is the same as clicking the Connect Signals/Slots toolbar. Click the Delete Clipping button and drag to the form; release. The Edit Connections dialog will appear. Click the clicked() signal and the deleteClipping() slot. Click OK.)