| 1669 | |
| 1670 | |
| 1671 | ResultType GuiType::ControlGetComboBox(ResultToken &aResultToken, GuiControlType &aControl, ValueModeType aMode) |
| 1672 | { |
| 1673 | LRESULT index = SendMessage(aControl.hwnd, CB_GETCURSEL, 0, 0); // Get index of currently selected item. |
| 1674 | |
| 1675 | // Problem #1: |
| 1676 | // It seems that any text put into a ComboBox's edit field by typing/pasting or via Ctrl.Text |
| 1677 | // always resets the current index to -1, even if that text exactly matches an existing item. |
| 1678 | |
| 1679 | // Problem #2: |
| 1680 | // CB_GETCURSEL sometimes returns an index which doesn't match what's in the Edit control. |
| 1681 | // This typically happens as a result of typing/pasting while the drop-down window is visible |
| 1682 | // and then canceling the dropdown without making a selection. |
| 1683 | // Note that when the CBN_SELENDCANCEL notification is received, CB_GETCURSEL correctly returns -1, |
| 1684 | // but at some point after that the control restores the index to a possibly incorrect value. This |
| 1685 | // appears to happen in other programs as well, and has been confirmed on Windows 10 and earlier. |
| 1686 | |
| 1687 | int edit_length = GetWindowTextLength(aControl.hwnd); // Zero is valid (the control may be empty). |
| 1688 | // For simplicity and in case the text is very large, use tmalloc() unconditionally |
| 1689 | // even though the text is probably very small. This allocation may be returned below. |
| 1690 | LPTSTR edit_text = tmalloc(edit_length + 1); |
| 1691 | if (!edit_text) |
| 1692 | _o_throw_oom; |
| 1693 | edit_length = GetWindowText(aControl.hwnd, edit_text, edit_length + 1); |
| 1694 | if (index != CB_ERR) |
| 1695 | { |
| 1696 | // The control returned a valid index, so verify that it matches the edit text. |
| 1697 | // If a search beginning at the indicated item does not return that item, it must |
| 1698 | // not be a match. This approach avoids retrieving the item's text, which reduces |
| 1699 | // code size. Performance hasn't been measured, but at a guess, is probably better |
| 1700 | // when the item matches (as it would 99.9% of the time), and worse in other cases. |
| 1701 | if (!edit_length) |
| 1702 | { |
| 1703 | // A different approach is needed in this case since CB_FINDSTRINGEXACT can't |
| 1704 | // search for an empty string. This preserves the ability to detect selection |
| 1705 | // of a specific empty list item, though for simplicity, clearing the edit text |
| 1706 | // will not match up to an empty list item. |
| 1707 | if (0 != SendMessage(aControl.hwnd, CB_GETLBTEXTLEN, (WPARAM)index, 0)) |
| 1708 | index = CB_ERR; |
| 1709 | } |
| 1710 | else |
| 1711 | { |
| 1712 | if (index != SendMessage(aControl.hwnd, CB_FINDSTRINGEXACT, index - 1, (LPARAM)edit_text)) |
| 1713 | index = CB_ERR; |
| 1714 | } |
| 1715 | } |
| 1716 | if (index == CB_ERR) |
| 1717 | { |
| 1718 | // Check if the Edit field contains text that exactly matches one of the items in the drop-list. |
| 1719 | // If it does, that item's position should be retrieved in AltSubmit mode (even for non-AltSubmit |
| 1720 | // mode, this should be done because the case of the item in the drop-list is usually preferable |
| 1721 | // to any varying case the user may have manually typed). |
| 1722 | index = SendMessage(aControl.hwnd, CB_FINDSTRINGEXACT, -1, (LPARAM)edit_text); // It's not case sensitive. |
| 1723 | if (index == CB_ERR && aMode != Value_Mode) // Text or Submit mode. |
| 1724 | { |
| 1725 | // edit_text does not match any of the list items, so just return the text. |
| 1726 | aResultToken.AcceptMem(edit_text, edit_length); |
| 1727 | return OK; |
| 1728 | } |
nothing calls this directly
no test coverage detected