Connecting Signals and Slots

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.)

Connecting Predefined Signals & Slots

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 the functionality we need 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'. Beneath 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 lots of 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.

Creating and Connecting Custom Slots

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 Qt 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 Qt Designer; the snippets are taken from the generated qt/tools/designer/examples/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 popup menu. (If there had been any existing variables the popup menu would also have a Delete option.) Type in 'QClipboard *cb;' and press Enter. 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 Enter. 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 Enter. 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. To invoke the code editor click a function, e.g. click init() in the Source tab of the Object Hierarchy.
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(); 
}
The first couple of lines change the LCD number's background color and make the form start with the focus in the line edit. We take a pointer to Qt's global clipboard and keep it in our class variable, cb. We connect the clipboard's dataChanged() signal to a slot called dataChanged(); we will create this slot ourselves shortly. If the clipboard supports selection (under the X Window system for example), we also connect the clipboard's selectionChanged() signal to a slot of the same name that we will create. Finally we call our dataChanged() slot to populate the line edit with the clipboard's text (if any) when the application begins.

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(); 
}
We take a copy of the clipboard's text and call our own clippingChanged() slot with the text we've retrieved. If the user has checked the Auto Add Clippings checkbox we call our addClipping() slot to add the clipping to the list box.

The selectionChanged() slot is only applicable under the X Window System. Users of MS Windows can still include the code to ensure that the application works cross-platform.
void MulticlipForm::selectionChanged()
{ 
    cb->setSelectionMode( TRUE );  
    dataChanged(); 
    cb->setSelectionMode( FALSE );  
}
We tell the clipboard to use selection mode, we call our dataChanged() slot to retrieve any selected text, then set the clipboard back to its default mode.

In the dataChanged() slot we called another custom slot, clippingChanged().
void MulticlipForm::clippingChanged( const QString & clipping )
{ 
    currentLineEdit->setText( clipping ); 
    lengthLCDNumber->display( (int)clipping.length() );  
}
We set the line edit to whatever text is passed to the clippingChanged() slot and update the LCD number with the length of the new text.

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 let Qt 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 );                
    } 
}
If there is some text we change the LCD's value to the length of the text. We then iterate over all the items in the list box to see if we have the same text already. If the text is not already in the list box we insert it.

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:

  1. 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.

  2. 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 ); 
            } 
        } 
    }
    The code for Copy Previous checks to see if there is a selected item in the list box. If there is the item is copied to the line edit. If we are using a system that supports selection we have to repeat the copy, this time with selection mode set. We don't explicitly update the clipboard. When the line edit's text is changed it emits a dataChanged() signal which our dataChanged() slot receives. Our dataChanged() slot updates the clipboard.

  3. 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.

  1. Click Edit|Slots to invoke the Edit Slots dialog. Click New Slot and replace the default name with 'deleteClipping()'. Click OK.

  2. 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() ); 
    }
    We call our own clippingChanged() slot with an empty string and use the list box's removeItem() function to remove the current item.

  3. 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 button. 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.)