Modeless Dialog Boxes

  1. Overview
    1. Modeless dialog boxes allow the user to interact with the parent window while the dialog box is still visible. These dialog boxes are best used for frequent, repetitive, on-going tasks.
    2. Usually changes made in the modeless dialog box are reflected in the parent window immediately to give the user an idea of how the change will affect the application
      1. To implement this functionality, the dialog box needs to trigger events that call event handlers in the parent window
      2. Because of this, modeless dialog boxes are more challenging to implement
  2. Observer Design Pattern
    1. The Observer Design Pattern is a common software design pattern in GUI programming
    2. Can be used with events and delegates to implement a modeless dialog box
    3. The parent window is the Observer, and the dialog box is the Subject
    4. Three steps are involved:
      1. Registration: The parent window tells the dialog box it would like to be notified about a particular event (like a radio button being selected)
      2. Notification: When the event occurs (a radio button is selected), the dialog box notifies the parent window of the change
      3. Deregistration: The parent window can tell the dialog box it is no longer interested in receiving notifications, but this step is usually never performed since the parent window is usually always interested in receiving event notifications from the dialog box until it is closed by the user
    5. MS documentation on events uses different termonology but same concept
      1. Publisher is the class that sends (or raises) an event (dialog box)
      2. Subscriber is the class that receives (or handles) an event (parent window)
  3. Implementation of a modeless dialog box
    1. In this example, we'll implement a spell check dialog box that allows the user to change the text in the dialog box and immediately see the changes in the parent window
    2. Create a new Windows Forms project in Visual Studio
    3. Add a new Windows Form called SpellCheckForm
    4. Add a text box and Close button as shown in the screenshot:
      spell check dialog box screenshot
    5. The dialog box (SpellCheckForm) must first declare a delegate, a reference to a method whose signature an event handler must match
      public delegate void ChangeWordEventHandler(string word);
      
    6. Next, the event must be defined (ChangeWord) that can be observed by the parent window. Later when the event is triggered, an event handler matching the ChangeWordEventHandler delegate will be notified of the event
      public event ChangeWordEventHandler ChangeWord;
      
    7. Add a TextChanged event handler to the text box that triggers the ChangeWord event, notifying the parent window of the word that has been typed:
      private void wordTextBox_TextChanged(object sender, EventArgs e)
      {
      	// Notify the parent window to changes in the text box
      	ChangeWord(wordTextBox.Text);
      }
      
    8. Finally add a Click event handler to the Close button so it closes the dialog box (setting its DialogResult will not work since the dialog box is not going to be shown modally)
      private void closeButton_Click(object sender, EventArgs e)
      {
      	Close();
      }
      
  4. Launching the modeless dialog box
    1. In your main form, drop a button and a text box which will be used for launching the modeless dialog box and showing the word that was typed in the dialog box
      spell check parent window screenshot
    2. When the button is pressed, register for the ChangeWord event and launch the dialog box using the Show() method:
      private void button1_Click(object sender, EventArgs e)
      {
      	SpellCheckForm spellChecker = new SpellCheckForm();
      	spellChecker.ChangeWord += spellChecker_ChangeWord;
      	spellChecker.Show();
      }
      
      Note that Show() returns immediately unlike ShowDialog() which blocks until the dialog box is closed
    3. Write the spellChecker_ChangeWord() event handler which will change the word in the text box to match the word in the dialog box:
      void spellChecker_ChangeWord(string word)
      {
      	wordTextBox.Text = word;
      }
      
    4. Alternatively, a lambda expression can be used to define an event handler
      spellChecker.ChangeWord += (word) =>
      {
      	wordTextBox.Text = word;
      };
      
    5. Run the application, press the Spell Check button to launch the dialog box, and observe the text box changing as you type in the dialog box's text box
  5. Improvements
    1. Comment-out the line
      // spellChecker.ChangeWord += spellChecker_ChangeWord;
      
      and run the application again. What happens when you launch the dialog and start typing? Fix this System.NullReferenceException by ensuring that ChangeWord is not null before "calling" it in the SpellCheckForm. Make sure you de-comment the line above before going on
      if (ChangeWord != null)
          ChangeWord(textBox1.Text);
      	
      // Or simplify with null-conditional operator
      ChangeWord?.Invoke(textBox1.Text);
      
    2. The spell check dialog box's purpose is to change the word typed in the main form into some other word. Modify the application so the dialog box is initially displaying the word typed in the main window by creating a property in SpellCheckForm:
      // Initialize the modeless dialog's text box
      spellChecker.TheText = label1.Text;
      

      TheText propery's setter also needs to set the dialog box's text box to the given text

    3. What happens if you press the Spell Check button more than once? Some solutions:
      1. Disable the Spell Check button once the dialog box has been launched. Disadvantage to this solution is the main form has to be alerted when the dialog is closed so it can re-enable the button
      2. Bring the dialog box to the front when the button is pressed
        private SpellCheckForm spellChecker;
        
        private void button1_Click(object sender, EventArgs e)
        {
        	if (spellChecker == null || spellChecker.IsDisposed)
        	{
        		// Was closed or hasn't been shown yet
        		spellChecker = new SpellCheckForm();
        		spellChecker.ChangeWord += spellChecker_ChangeWord;
        		
        		// Initialize the modeless dialog's text box
        		spellChecker.TheText = label1.Text;
        	}            
        	else
        	{
        		// Already being displayed
        		spellChecker.BringToFront();
        	}
        
        	spellChecker.Show();
        }