65.9K
CodeProject is changing. Read more.
Home

Implementing Dynamic ToolTips: Preventing SetToolTip stack overflow

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.43/5 (4 votes)

Feb 17, 2010

CPOL
viewsIcon

21166

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