Windows Forms Combobox issueMarch 31, 2009

The issue


About a week ago I was adding character casing support to a custom control that inherits from ComboBox when I discovered what appears to be a bug in that control. I did some web research on it and found several other people had encountered the issue and a very old confirmed bug report in Microsoft’s bug tracker.

The issue occurs when the ComboBox is set to DropDown as the DropDownStyle and the control first receives focus. The Text property is not being set in proper sequence when the user types the very first character into the edit region. Any code in the OnTextChanged or OnTextUpdate routines will see the Text field as the old value of the edit region. That is, it is as if the change of the Text property has not occurred until after both of those routines have completed. When subsequent characters are typed, the Text property will be set in the correct sequence.

The effect in my casing routine was that the first character typed into the edit region was never cased by my character casing routine. It was also breaking other logic that I had in the OnTextChanged routine. I noticed that I could visually see the edit region text change to the correct value before breaking in OnTextChanged routine, and yet the Text property still contained the old value.

Solution


The MS bug tracker entry contains a workaround to this issue: they suggest using the AccessibilityObject.Value of the control to get the true value of the text. This is a poor solution because that value will be the Text value stripped of certain characters. In my case it stripped out ampersands.

This is the solution at which I arrived after considerable tinkering with the WindowsAPI layer. I was hoping for something more elegant, but this is still a solid solution as far as I can test.

        // This is the flag used to prevent an infinite loop when setting text.
        bool _inTextCheck = false;
        protected override void OnTextUpdate(EventArgs e)
        {
            if (!_inTextCheck)
            {

int oldStart = SelectionStart; int oldLength = SelectionLength; string workingText = Text; WindowsAPI.ComboBoxInfo boxInfo = new WindowsAPI.ComboBoxInfo(); boxInfo.cbSize = Marshal.SizeOf(boxInfo); WindowsAPI.GetComboBoxInfo(Handle, ref boxInfo); int boxHandle = boxInfo.hwndEdit.ToInt32(); int capacity = WindowsAPI.GetWindowTextLength(boxHandle); if (capacity > 0) { StringBuilder trueText = new StringBuilder(capacity); WindowsAPI.GetWindowText(boxHandle, trueText, capacity + 1); if (trueText.ToString() != workingText) { workingText = trueText.ToString(); } } // The following provides my character casing workingText = Casing.FixCase(workingText, CharCasing); _inTextCheck = true; Text = workingText; _inTextCheck = false; SelectionStart = oldStart; SelectionLength = oldLength; } base.OnTextUpdate(e); }

If you have code on the OnTextChanged routine you will need to make sure that it doesn’t run multiple times by adding a test for _inTextCheck.

Alan Henager @ 9:32 AM

Comment