Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Implementing Dynamic ToolTips: Preventing SetToolTip stack overflow

3.43/5 (4 votes)
17 Feb 2010CPOL 21.2K  
Handling the Popup event raised by the System.Windows.Forms.ToolTip component would seem offer an ideal opportunity to alter the tooltip text to reflect an underlying change. Unfortunately the stack overflow caused by calling ToolTip.SetToolTip will soon put a stop to the attempt.The test...
Handling the Popup event raised by the System.Windows.Forms.ToolTip component would seem offer an ideal opportunity to alter the tooltip text to reflect an underlying change. Unfortunately the stack overflow caused by calling ToolTip.SetToolTip will soon put a stop to the attempt.

The test programme to investigate this issue creates a form containing two buttons, one of which will have dynamic tip strings. These are randomly selected from an array by the NewTipText method.
C#
public partial class Form1 : Form {
  ToolTip toolTip1;
  String[] tipText;
  Random rnd;
  Boolean recursionBreak;

  public Form1() {
    InitializeComponent();
    InitialiseToolTips();
    // use this code file as the source of strings
    tipText = File.ReadAllLines(@"..\..\Form1.cs");
    rnd = new Random();
  }

  private void InitialiseToolTips() {
    toolTip1 = new ToolTip();
    toolTip1.SetToolTip(button1, "Fixed text");
    // initialisation of button2's tip is necessary
    // even though this text will never be shown
    toolTip1.SetToolTip(button2, "Variable text");
    toolTip1.Popup += toolTip1_Popup;
    recursionBreak = false;
  }

  private String NewTipText() {
    // randomly select a string from the array
    Int32 idx = rnd.Next(0, tipText.Length);
    return tipText[idx];
  }

  private void toolTip1_Popup(object sender, PopupEventArgs e) {
    Debug.Print("POPUP {0}", e.AssociatedControl.Name);
    if (recursionBreak) {
      Debug.Print("  BREAK");
      return;
    }
    if (e.AssociatedControl == button2) {
      String proposed = NewTipText();
      Debug.Print("  NEW TEXT '{0}'", proposed);
      if (String.IsNullOrEmpty(proposed)) {
        Debug.Print("  CANCELLED");
        e.Cancel = true;
      } else {
        ToolTip tt = (ToolTip)sender;
        if (proposed != tt.GetToolTip(button2)) {
          recursionBreak = true;
          tt.SetToolTip(button2, proposed);
          recursionBreak = false;
        } else {
          Debug.Print("  UNCHANGED");
        }
      }
    }
  }

Preventing Recursion

SetToolTip reraises the Popup event internally when it is called from within the event handler and unless steps are taken to break this cycle a stack overflow is inevitable. In the example the boolean recursionBreak takes care of this although temporarily detaching the event handler would have the same effect.
C#
tt.Popup -= toolTip1_Popup;
tt.SetToolTip(button2, proposed);
tt.Popup += toolTip1_Popup;

The Event Conundrum

Setting a control's tooltip dynamically requires a popup event, but the popup events for the control will stop if SetToolTip is called with an empty string argument. When NewTipText() returns an empty string, the tooltip display is suppressed by setting e.Cancel to true, and crucially SetToolTip is not called thus ensuring that events will continue to be raised.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)