8.3. Dialog Modality—Modal or Modeless Dialogs

A dialog can be used in two ways. Basically, it either blocks access (by mouse or keyboard) to any other parts if an application while visible, or it does not impose any restrictions to what the user can do. The first approach is known as modal dialog behavior, and the second is modeless dialog behavior.

The decision when to use a modal or modeless dialog depends very much on the dialog itself and on what the purpose of the dialog is. At one extreme: In a situation where you cannot do any useful work until a decision has been made and the dialog has been closed, you can safely go for a modal dialog. A file selector is a typical example and is often implemented as a modal dialog. At the opposite end of the scale, a search dialog is modeless in most cases. Modeless dialogs introduce greater flexibility for end users because they are not forced to work in a specific pattern decided by the developer. The cost for the greater flexibility is that the code required to make a modeless dialog work as intended can be somewhat more complicated. However, if it is possible, you will normally get a better result using modeless dialogs. The standard KDE file selector can be used as a modeless dialog, and a dialog derived from the KDialogBase class can be either modal or modeless.

For a developer, the main differences between modal and modeless dialogs are how the dialog becomes visible and how it can transfer information—that is, the dialog settings—to the rest of the program. Listing 8.5 illustrates how the KDE color selector is used as a modal dialog. It is modal because the last argument is true (line 4 in Listing 8.5). Every modal dialog must use QDialog::exec(). This is a method that starts the dialog and returns the result only after the dialog is once again hidden. The QDialog::exec() on line 8 blocks access to any other part of the program but the dialog while it is active.


Example 8.5. A Modal Dialog Located on the Stack

   1 
   2  1: int
   3  2: getColor( QColor &theColor, QWidget *parent )
   4  3: {
   5  4:    KColorDialog dialog( parent, "colordialog", true );
   6  5:    if( theColor.isValid() )
   7  6:    {
   8  7:       dialog.setColor( theColor );
   9  8:    }
  10  9:    int result = dialog.exec();
  11 10:    if( result == Accepted )
  12 11:    {
  13 12:       theColor = dialog.color();
  14 13:    }
  15 14:    return result;
  16 15: }
  17 

The dialog state is in this case collected by the color() method. Note that since the dialog object is stored on the stack, the dialog is automatically destroyed when the function returns. A modal dialog does not have to be located at the stack while it is in use. Many developers prefer the implementation shown in Listing 8.6. This can be a very important design decision if the dialog object is so large that a stack overflow could otherwise occur. Make sure that the dialog object is removed from memory (as shown on line 20) before the function returns. Otherwise, you will have a memory leak (bug) in your program.


Example 8.6. A Modal Dialog Allocated from the Heap

   1 
   2  1: int
   3  2: getColor( QColor &theColor, QWidget *parent )
   4  3: {
   5  4:    KColorDialog *dialog = new KColorDialog( parent, "colordialog", true );
   6  5:    if( dialog == 0 )
   7  6:    {
   8  7:       return Rejected; // Rejected is a constant defined in QDialog
   9  8:    }
  10  9:
  11 10:    if( theColor.isValid() )
  12 11:    {
  13 12:       dialog->setColor( theColor );
  14 13:    }
  15 14:    int result = dialog->exec();
  16 15:    if( result == Accepted )
  17 16:    {
  18 17:       theColor = dialog->color();
  19 18:    }
  20 19:
  21 20:    delete dialog; // Important to avoid memory leaks
  22 21:   
  23 22:    return result;
  24 23: }
  25 

When you want to use a modeless dialog, you have to do two things. First, you must allocate the dialog object from the heap (with new); second, you must make it visible with show(). When you call show() on a dialog, the method starts the dialog and then returns immediately, not waiting for the dialog to be hidden. To avoid a memory leak, you must now store the pointer as well so that you can release the memory occupied by the dialog code when the dialog is no longer needed. Listing 8.7 shows how the CGotoDialog dialog class (see Listing 8.3) of KHexEdit is used as a modeless dialog.


Example 8.7. A Modeless Dialog

   1 
   2  1: CHexEditorWidget::CHexEditorWidget()
   3  2: {
   4  3:   mGotoDialog = 0;
   5  4: }
   6  5:
   7  6: CHexEditorWidget::~CHexEditorWidget()
   8  7: {
   9  8:   delete mGotoDialog;
  10  9: }
  11 10:
  12 11: void
  13 12: CHexEditorWidget::gotoOffset()
  14 13: {
  15 14:   if( mGotoDialog == 0 )
  16 15:   {
  17 16:     mGotoDialog = new CGotoDialog( topLevelWidget(), "goto", false );
  18 17:     if( mGotoDialog == 0 )
  19 18:     {
  20 19:       return;
  21 20:     }
  22 21:     connect( mGotoDialog, SIGNAL(gotoOffset( uint, uint, bool, bool )),
  23 22:            mHexView, SLOT(gotoOffset( uint, uint, bool, bool )) );
  24 23:   }
  25 24:   mGotoDialog->show();
  26 25: }
  27 

The mGotoDialog is a pointer stored in the CHexEditorWidget class, and it is initialized to 0 in the constructor and deleted in the destructor of CHexEditorWidget. The first time the dialog is used, it is first allocated (line 16) and next started ( with show() on line 24). Then, the next time it is used, it is only started. The gotoOffset() function returns immediately after the dialog has been started.

When a dialog is modeless, other parts of a program have to be notified when the dialog settings are ready to be used. Because the show() returns immediately, the dialog must emit a signal to indicate that the data is ready. In Listing 8.7, the gotoOffset(…) signal of the CGotoDialog class is emitted for this purpose.

8.3.1. Removal of Modeless Dialogs

How and when can modeless dialogs be removed from memory? First, you must store a pointer to the allocated dialog object. The memory can be released only when the dialog is no longer needed (hidden). This can be done when one of these two criteria is met:

  1. The application terminates or the parent widget is destroyed.

  2. The dialog becomes hidden or is closed.

Option 1 is by far the simplest and is, perhaps, the best way to handle a modeless dialog. It has the advantage that you can easily hide and redisplay a dialog on the same position on the screen (which many users prefer) without extra coding. The biggest disadvantage is that it remains in memory even when it is not visible. Normally, this is done as with the dialog in Listing 8.7. The dialog object is destroyed in the destructor of the object that stores the pointer to the dialog.

Option 2 can be the best option if your dialog can be created quickly or is rarely used. However, if it takes a long time to prepare and set up the dialog and its contents, or if it is a lightweight dialog (uses little memory) or is used frequently, this may not be the best option.

You should never delete the dialog from within the dialog code itself. This means that delete this is never a safe way to do it, unless you know exactly what you do and how the library code you use works. Listing 8.8 illustrates a dangerous attempt to release the memory the Goto dialog has allocated when the Cancel button has been activated.


Example 8.8. This Can Make Your Code Buggy!

   1 
   2 1: void
   3 2: CGotoDialog::slotCancel()
   4 3: {
   5 4:   hide();       // Ok, will hide the dialog
   6 5:   delete this;  // Bad!
   7 6: }
   8 

The problem with this code is that the slot is connected to a signal in the Cancel button. Remember this: When a signal is emitted from an object, it returns only after every slot method it is connected to has finished. If one of these destroys the dialog and thereby the button itself, anything can happen on the return—and perhaps even before that—because the internal variables of the button object are no longer valid. The real danger is that it can sometimes work and sometimes it can cause a segmentation fault later.

To simplify the destruction procedure, the KDialogBase class emits a signal, KDialogBase::hidden(), whenever it receives a QHideEvent (becomes hidden). You can use this signal to start the destruction process. However, the same restriction applies to the slot function you used to connect to the hidden() signal in the previous example with the Cancel button. One common solution to avoiding this problem is to activate a one-shot timer with a zero delay. This is a safe method because even with a delay equal to zero, the timer function can be executed only when the program again runs in the main event loop. This can happen only after the signal has returned. Listing 8.9 shows how a modeless option dialog in KJots is destroyed this way. KJots is a KDE utility application that is used to manage short text notes.


Example 8.9. A Secure Way to Remove a Modeless Dialog Object from Memory After It Has Been Hidden

   1 
   2 1: void
   3 2: KJotsMain::configure()
   4 3: {
   5 4:   if( mOptionDialog == 0 )
   6 5:   {
   7 6:     mOptionDialog = new ConfigureDialog( topLevelWidget(), 0, false );
   8 7:     if( mOptionDialog == 0 )
   9 8:     {
  10 9:       return;
  11 10:     }
  12 11:     connect( mOptionDialog, SIGNAL(hidden()),this,SLOT(configureHide()));
  13 12:     connect( mOptionDialog, SIGNAL(valueChanged()),
  14 13:                 this, SLOT(updateConfiguration()) );
  15 14:   }
  16 15:   mOptionDialog->show();
  17 16: }
  18 17:
  19 18: void
  20 19: KJotsMain::configureHide()
  21 20: {
  22 21:   QTimer::singleShot( 0, this, SLOT(configureDestroy()) ); // Zero delay
  23 22: }
  24 23:
  25 24: void
  26 25: KJotsMain::configureDestroy()
  27 26: {
  28 27:   if( mOptionDialog != 0 && mOptionDialog->isVisible() == false )
  29 28:   {
  30 29:     delete mOptionDialog;
  31 30:     mOptionDialog = 0;
  32 31:   }
  33 32: }
  34 

If you think this was a tedious method, then KDialogBase can help you with this as well. There is a function named KDialogBase::delayedDestruct() that automates the destruction process. This function will do a delete this but in a controlled fashion. You can call this function from the slots that normally hide the dialog. Note however that if you have stored a pointer to the dialog object outside the class as in Listing 8.9, then this pointer becomes a dangerous dangling pointer once the dialog has destroyed itself. You can solve this problem by using the Qt QGuardedPtr class to protect the external pointer.