views:

303

answers:

3

Hello everyone!

I've got a standard TextBox control which I'm trying to have mimic the "soft descriptions" like those found in the title and tags boxes on StackOverflow. Essentially, when the user's focus enters the control, it hides the description ("Username") in this case, and sets alignment and color to be that of a standard text control. When the user leaves the textbox, I want to check if the user actually entered anything, and put the username display back up otherwise.

For example:

    private void tbUsername_Enter(object sender, EventArgs e)
    {
        if (tbUsername.TextAlign == HorizontalAlignment.Center)
        {
            tbUsername.TextAlign = HorizontalAlignment.Left;
            tbUsername.ForeColor = SystemColors.ControlText;
            tbUsername.Text = String.Empty;
        }
    }

    private void tbUsername_Leave(object sender, EventArgs e)
    {
        if (tbUsername.Text == String.Empty)
        {
            tbUsername.TextAlign = HorizontalAlignment.Center;
            tbUsername.ForeColor = SystemColors.InactiveCaption;
            tbUsername.Text = "Username";
        }
    }

Unfortunately, when I setup these events, the user cannot tab out of the username control. The control simply flickers and control returns to the textbox control itself until the user has entered something, skipping over the event body.

If I call this.SelectNextControl() in the event, then the event enters an infinite loop.

Does anybody see what I'm doing wrong?

+2  A: 

Setting the TextAlign property on the TextBox control returns focus back to that control. That seems like a bug.

Here is a quick fix:

tbUsername.Enabled = false;
tbUsername.ForeColor = SystemColors.InactiveCaption;
tbUsername.Text = "Username";
tbUsername.TextAlign = HorizontalAlignment.Center;
tbUsername.Enabled = true;

(although somewhat of a hack around unexpected behavior). Simply disable the control before changing the alignment. Another "fix" would be to keep things aligned left, or to measure how many spaces to insert to simulate centering the text.

Bill
+1 :) If nothing else, I have to upvote a fellow Bill! Only reason I used the `focus` method instead is it flickered a bit less on my machine.
Billy ONeal
+3  A: 

Looks like another way around it (Using Reflector to see that it does refocus back on the Control if the focus was there to begin with). I think it is a bug, but looks like they were just reusing RecreateHandleCore function to redraw the text. So another way would be to focus off the textbox first, then continue:

  private void LeaveEvent(object sender, EventArgs e)
  {
     if (String.IsNullOrEmpty(tbUsername.Text))
     {
        tbUsername.Text = USER_NAME;
        tbUsername.ForeColor = SystemColors.InactiveCaption;
        this.Focus();
        tbUsername.TextAlign = HorizontalAlignment.Center;
     }
  }
SwDevMan81
+1 for taking the time to check what is actually going on in Reflector.
Bill
+2  A: 

Use BeginInvoke

    private void tbUsername_Leave(object sender, EventArgs e)
    {
        BeginInvoke(new MethodInvoker(OnLeave));
    }

    private void OnLeave()
    {
        if (tbUsername.Text == String.Empty)
        {
            tbUsername.TextAlign = HorizontalAlignment.Center;
            tbUsername.ForeColor = SystemColors.InactiveCaption;
            tbUsername.Text = "Username";
        }
    }
Jacob Seleznev
+1. The only reason I chose the focus() solution over this one is that it delocalizes what the function should be doing from it's definition.
Billy ONeal
This solution works mostly "by accident", i.e., normally the BeginInvoke delegate would get scheduled to be called "later", that is, after focus is moved away from the control - but it is not guarenteed to be later - the OnLeave code could actually run before the tbUsername_Leave event handler completes and focus moves away from the original control.
Bill
Where is the proof that it is not guaranteed?I can only re-state that BeginInvoke is mostly equivalent to PostMessage and enqueues a registered message on the windows message queue for later processing. It’s then picked up by the applications message pump and executed on the main GUI thread.
Jacob Seleznev