How many times has code like this appeared on the DailyWTF



  • I had need to create a custom control (in C#).  The control has settings for displaying a "dash" within a number to make it easier for a user to read. The number comes from a database sequence (and will always be 7 digits long).  So the number will be formated as nnn-nnnn .  But wait, I haven't gotten to the "good" part.  I happily derived from System.Windows.Forms.Textbox and overrode the "Text" property to insert the dash as the user types.  Everything is working great....

    Then I tried copy/pasting a number into the control...no dash!  I'm thinking, O.K.  obviously the OnKeyPress doesn't happen with a paste.  No problem, I'll find a paste event or the equivalent. I'm sure there is one, but I still haven't found it...But wait, this still isn't the WTF moment....

    On a hunch, I tried setting the Text value to itself when the tab or return is hit....PRESTO! The dash appears.   So now I have code that looks like some that has been ridiculed on this site as "voodoo" coding.  But this actually works!   For your further amusement, here is the relevent methods: 

    public class HypenatedTextBox : TextBox
     {
           <snip>

        /// <summary>
        /// When Text is set for Hypenated Textbox, we make sure to add the dash
        /// </summary>
        public override string Text
        {
            get
            {
                var s = base.Text;
                return s.Replace("-", "");
            }
            set
            {
                string hyphValue = value.Replace("-","");       // First get rid of any dash in the incoming characters
                if (value.Length >= DashPosition)
                {
                    hyphValue = value.Substring(0, DashPosition) + "-" + ((value.Length == DashPosition) ? "" : value.Substring(DashPosition));
                }
                else
                {
                    hyphValue = value;          // Text isn't long enough to show the dash yet.
                }
                base.Text = hyphValue;
            }
        }

        /// <summary>
        /// This method makes sure that any key entry is either a digit or dash.  A carriage return or tab
        /// causes the "NumberComplete" event to be triggered, rather than moving to a new field.  That's why
        /// the acceptstabs and acceptsnewline must be set (so we can see them here).
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            string newText = Text;

            if (char.IsDigit(e.KeyChar) || e.KeyChar == '-')
            {
                e.Handled = true;
                if (this.SelectionLength > 0)
                {
                    // Delete the Selected characters!
                    newText = "";
                }
                newText += e.KeyChar.ToString();
                Text = newText;
                SelectionStart = Text.Length + 1;
            }

            if (e.KeyChar == '\t' || e.KeyChar == '\n' || e.KeyChar == '\r')
            {
                e.Handled = true;
                if (isValid())
                {
                    Text = Text;                    // OMG!  This works to add a dash when text is copied/pasted into the control
                   
                    SelectAll(); // We're done, so it should be all selected for the next value to erase
                    // Raise the Event
                    InvokeNumberCompleteEvent(Value);
                }
                else
                {
                    InvokeNumberErrorevent(Text);
                }
            }

                  // No need to call the base method, we've handled (or thrown away) every character.
            //base.OnKeyPress(e);           
        }
    }


     

     



  • @Auction_God said:

    Then I tried copy/pasting a number into the control...no dash!  I'm thinking, O.K.  obviously the OnKeyPress doesn't happen with a paste.  No problem, I'll find a paste event or the equivalent. I'm sure there is one, but I still haven't found it.

    Isn't there a TextChanged event?  If what your doing isn't directly related to a type of user input such as clicking, typing, moving, etc. then you should always be looking for the more general "changed" events like SelectionChanged, TextChanged, etc.



  • Oh well, I guess a simple MaskedTextBox wasn't fancy enough. Better waste time developing a custom shoestring-and-plaster control and then spout arogantly about your own bungling...

    WTF indeed...



  •  TRWTF is languages in which setting a variable to itself accomplishes anything useful.



  •  How about if the user uses the mouse instead? Eg. right click -> paste and then click elsewhere? Your voodoo code doesn't run then... 



  • Elementary, dear Watson.

    1. Text = Text inserts a dash, because it feeds the output of your getter that removes all dashes to your setter that adds dash in correct place. So it obviously adds the dash.
    2. Paste is probably implemented by the underlying standard runtime, so it does not call the Text setter and therefore can't insert the dash. Follows from that you have to do it yourself (and there is no excuse for not looking up which event is fired when the text changes and not using it).
    I don't see any WTF except that your idea there is one.


  • @Bulb said:

    Paste is probably implemented by the underlying standard runtime, so it does not call the Text setter and therefore can't insert the dash. Follows from that you have to do it yourself (and there is no excuse for not looking up which event is fired when the text changes and not using it).

    TRWTF is that we have an object-oriented language that supports properties and allows overriding them, but later ignores and bypasses such overrides.


  • Funny as it's these kinds of things that people hate VB6 for.



  • Why do you say that? It does use the overrides, that's why it works.



  • @EmhMk2 said:

    Oh well, I guess a simple MaskedTextBox wasn't fancy enough. Better waste time developing a custom shoestring-and-plaster control and then spout arogantly about your own bungling...

    WTF indeed...

     

    I actually was using a masked text box.  Unfortunately the design of that control really sucks.  It insists on displaying "place-holder" characters, even when the field is empty.  If you attempt to click into the control (to give it focus), the insertion bar does not go to the beginning of the control - it is wherever in the control you happened to click.   I guess your WTF moment was assuming that I was too stupid or lazy to have tried using it.

    Other posters have suggested that I should hunt down the actual event that gets fired on paste.  I will do so, but it was late last night, and decided to try the code that I did post.  I *thought* that it would work the way that it does - I also thought that my fellow WTF'ers would get a kick out of what most programmers would automatically say was a "do-nothing" instruction.



  • @Soviut said:

    Isn't there a TextChanged event?  If what your doing isn't directly related to a type of user input such as clicking, typing, moving, etc. then you should always be looking for the more general "changed" events like SelectionChanged, TextChanged, etc.

    Indeed there is, but this would ALSO fire after I update the contents as the user types.  Essentially running the code twice - it won't hurt anything, but not very elegant.  I need an event that will only fire on a paste operation.



  • HypenatedTextBox, an otherwise ordinary TextBox, but with a lot of exaggerated publicity surround it.


  • :belt_onion:

    @Auction_God said:

    @Soviut said:
    Isn't there a TextChanged event?  If what your doing isn't directly related to a type of user input such as clicking, typing, moving, etc. then you should always be looking for the more general "changed" events like SelectionChanged, TextChanged, etc.
    Indeed there is, but this would ALSO fire after I update the contents as the user types.  Essentially running the code twice - it won't hurt anything, but not very elegant.  I need an event that will only fire on a paste operation.
    The advantage of using the textchanged event is that you have to write your code once and it will work for whatever way the user inserts his input. I wouldn't care too much about the performance of the eventhandler because the user probably isn't typing fast enough to create a bottleneck here.



  • @EmhMk2 said:

    Oh well, I guess a simple MaskedTextBox wasn't fancy enough.

     

    The simple MaskedTextBox works great but looks ugly. I have considered using it but was turned off by the underscores.



  • @joemck said:

     TRWTF is languages in which setting a variable to itself accomplishes anything useful.

     

     It's not a variable, it's just syntatic sugar for method calls. Now if you said that TRWTF are languages that make calling a method look indistinguishable from seting a variable, I'd agree with you. 



  • @Sad Bug Killer said:

    @joemck said:

     TRWTF is languages in which setting a variable to itself accomplishes anything useful.

     

     It's not a variable, it's just syntatic sugar for method calls. Now if you said that TRWTF are languages that make calling a method look indistinguishable from seting a variable, I'd agree with you. 

    Seriously?  Properties are beautiful.  You're breaking my heart, here...  D:



  • @Zecc said:

    HypenatedTextBox, an otherwise ordinary TextBox, but with a lot of exaggerated publicity surround it.

     

    Heh.  Funny thing is that the author appears to know the correct spelling (see 'hyphValue') but managed to get it wrong in both the method name and comment.



  • @Auction_God said:

    @EmhMk2 said:

    Oh well, I guess a simple MaskedTextBox wasn't fancy enough. Better waste time developing a custom shoestring-and-plaster control and then spout arogantly about your own bungling...

    WTF indeed...

     

    I actually was using a masked text box.  Unfortunately the design of that control really sucks.  It insists on displaying "place-holder" characters, even when the field is empty.  If you attempt to click into the control (to give it focus), the insertion bar does not go to the beginning of the control - it is wherever in the control you happened to click.   I guess your WTF moment was assuming that I was too stupid or lazy to have tried using it.

    Other posters have suggested that I should hunt down the actual event that gets fired on paste.  I will do so, but it was late last night, and decided to try the code that I did post.  I *thought* that it would work the way that it does - I also thought that my fellow WTF'ers would get a kick out of what most programmers would automatically say was a "do-nothing" instruction.

     

     

    Use the MaskedEditExtender in the AjaxControlToolkit.  Fixes all those problems rather fluidly.  Set "PromptCharacter" to " " if you don't like to see placeholders.  It allows you to put the cursor wherever you want it with the "InputDirection" attribute and use just about whatever masks you'd want.  There's probably another 30 attributes there as well if you want to get nuts with it.  I'm not thrilled with the currency behavior on it, as I think it could be a little confusing for users if they make a typo and try to change it, but everything else works great.  



  • @Auction_God said:

    @Soviut said:
    Isn't there a TextChanged event?  If what your doing isn't directly related to a type of user input such as clicking, typing, moving, etc. then you should always be looking for the more general "changed" events like SelectionChanged, TextChanged, etc.

    Indeed there is, but this would ALSO fire after I update the contents as the user types. Essentially running the code twice - it won't hurt anything, but not very elegant. I need an event that will only fire on a paste operation.

    public class HypenatedTextBox : TextBox
    {
    private string lastValidValue = string.Empty;
    public char HyphenCharacter = '-';

    protected string Hyphenate(string s)
    {
    s = s.Replace(this.HyphenCharacter.ToString(), string.Empty);
    string ret = string.Empty;

    while(s.Length > this.GroupLength)
    {
    int groupSize = Math.Max(s.Length, this.GroupLength);
    ret += s.SubString(0, groupSize) + this.HyphenCharacter.ToString();
    s = s.SubString(groupSize);
    }
    ret += s;

    return ret;
    }

    protected override void OnTextChanged(Eventargs e)
    {
    /* Re-hyphenate the text, which may return an equal string. */
    string hyphenatedText = Hyphenate(this.Text);

    if (hyphenatedText != this.Text)
    {
    /* Causes a cascade which will re-run OnTextChanged a second time */
    this.Text = hyphenatedText;
    }
    else
    {
    /* The second or third time the text value will have stabilized and we can
    perform validation */

    if (this.isValid())
    {
    /* With a valid value we can go up the chain of inheritance and fire
    any attached TextChanged events. Either we've entered a valid value
    or we arrive here after the last known valid value has been restored.
    */
    lastValidValue = this.Text;
    base.OnTextChanged(e);
    }
    else
    {
    /* Causes a cascade which will re-run OnTextChanged a third time */
    this.Text = lastValidValue;
    }
    }
    }

    protected override void OnKeyPress(KeyPressEventArgs e)
    {
    if(char.IsDigit(e.KeyChar) || e.KeyChar == this.HyphenCharacter)
    {
    base.OnKeyPress(e);
    }
    else
    {
    e.Handled = true;
    }
    }
    }

    A somewhat more sane design where you don't have to fuddle with paste operations, etc. and that will at worst cascade OnTextChanged three times, but won't cascade the base.OnTextChanged or any attached TextChanged events!

    It is however likely that you will have to remember and restore the caret position. Iirc TextBox has a simple SelectionStart property to manage this. You should be able to infer the correct caret position by splitting your hyphenation into a 'before the caret' and an 'after the caret' part. That way you can compare lengths before and after re-hyphenation, which will allow you to adjust the caret to any additional hyphens. E.g. 1234|56 -> 123-4|56 rather than 123-|456.

    I'll leave that last part as an exercise to the reader. ;)



  • You're having to hack your way through this because you don't understand the event system.

    TextBox is just a wrapper for a native Windows control.  You can go ahead and override the Text property, but typing (or pasting) into the text box itself does not invoke this property.  The event you should be looking at is OnTextChanged, which is fired on a paste or any other type of change.

    I'll grant you that getting this "perfect" is not as easy as it sounds, but this is still god-awful.  For example:

    • It auto-inserts the hyphen, but doesn't auto-delete it on backspace;
    • It totally mangles input processing when the cursor isn't at the end (type one digit, then Home, then type another digit... it appears at the end);
    • It happily lets you delete the hyphen with the delete or backspace key, and puts it back at an indeterminate time later on;
    • It does extremely weird things when you insert a hyphen when there is already a hyphen;
    • It inserts two hyphens (an extra one at the end) when you actually type in a hyphen - very annoying if inserting a hyphen in the middle;
    • If you highlight, for example, 2 digits out of 8, then try to type over them, it clears the entire field;
    • It messes with the cursor position at various instances;
    • It doesn't actually prevent typing of letters or other special characters (I assume you check this in the validation method, but you should just be preventing them in the first place)

    This is why you need to evaluate your options carefully when you're considering rolling custom components.  You could have gotten almost the same result with a MaskedTextBox and just changed the PromptChar to a space if you don't like the underscores.  If that's not good enough, fine, make your own, but just make sure that in the process of trying to get one feature that you want, you don't end up with frustrating useless garbage like Community Server that lacks half of the standard features users are going to expect and mangles half of the remaining ones.

    Just for the record, there is no paste event, but it's easy enough to hook by overriding WndProc and catching WM_PASTE.  Winforms does a lot of things but it doesn't wipe your ass for you; don't be helpless, if you can't find the specific event or method then use the Windows API.


  • @Auction_God said:

    I actually was using a masked text box.  Unfortunately the design of that control really sucks.  It insists on displaying "place-holder" characters, even when the field is empty. 

    Oh yeah - setting " " as PromptChar would be really hard... Or setting HidePromptOnLeave to true... Oh well, you could even use a proportional font if you don't like the resulting skipping of the hyphen on enter/leave.

    @Auction_God said:

    If you attempt to click into the control (to give it focus), the insertion bar does not go to the beginning of the control - it is wherever in the control you happened to click.  

    Hmm, there are Enter and Click events there for a reason. Nothing prevents you from setting the Cursor/Selection to whatever you want in them.

    @Auction_God said:


    I guess your WTF moment was assuming that I was too stupid or lazy to have tried using it.

    Well, there's that - but also your slightly smug attempt to simultaneously portray your 'solution' as necessary evil, while still being somewhat clever.

     

     



  • @Bulb said:

    Text = Text inserts a dash, because it feeds the output of your getter that removes all dashes to your setter that adds dash in correct place. So it obviously adds the dash.

        Exactly - its not a simple string assignment, and illustrates the power of  class-based programming  @Bulb said:

    I don't see any WTF except that your idea there is one.
         :)

     

                                                                                                                                                                                                                                                                                      



  •  So let me get this straight.

    You discovered that you had a string, that didn't have a dash in it.

    So you wrote some code to take a string and add a dash.

    Later, you discovered a case where the string didn't have a dash in it.

    So you decided to...

    CALL the function you write to the dash?

    you da man!


Log in to reply