{"id":166,"date":"2007-06-17T23:16:30","date_gmt":"2007-06-17T23:16:30","guid":{"rendered":"http:\/\/dalelane.co.uk\/blog\/?p=166"},"modified":"2008-03-13T19:56:23","modified_gmt":"2008-03-13T19:56:23","slug":"autocomplete-with-a-netcf-combobox","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=166","title":{"rendered":"AutoComplete with a .NETCF ComboBox"},"content":{"rendered":"<p><a target=\"_blank\" href=\"http:\/\/msdn2.microsoft.com\/en-gb\/netframework\/aa497273.aspx\">.NET Compact Framework<\/a> doesn&#8217;t support auto-complete in text entry controls. I didn&#8217;t realise that until I wanted it for a Form I was throwing together tonight, but there you go. <\/p>\n<p>So, in the spirit of <a href=\"http:\/\/hackday.org\/\" target=\"_blank\">Hack Day<\/a> (<em>which I&#8217;m still gutted to have missed!<\/em>) I had a quick stab at throwing together something myself. <\/p>\n<p>Read on to see what I mean, and how I did it. (And to tell me a better way to do it!)<\/p>\n<p><!--more--><\/p>\n<h4>What <u>is<\/u> available<\/h4>\n<p>To start with, a quick recap on what is provided, because you can get some things a bit similar:<\/p>\n<ul>\n<li>a drop down list of strings to choose from &#8211; (using a ComboBox with a DropDownStyle of DropDownList)\n<ul>\n<li>clicking on the control with the stylus provides a scrollable list to choose from<\/li>\n<li>pressing a key on the keyboard will cycle through all items in the list which begin with that first letter (if there are 20 words beginning with &#8216;T&#8217;, you need to press T fourteen times to get the fourteen item in the list)<\/li>\n<\/ul>\n<\/li>\n<li>a drop down list of strings to choose from, allowing new items not already on the list to be entered  &#8211; (using a ComboBox with a DropDownStyle of DropDown)\n<ul>\n<li>clicking on the drop-down arrow with the stylus provides a scrollable list to choose from<\/li>\n<li>using the keyboard allows new entries to be entered, ignoring the list<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>So what do I want?<\/h4>\n<p>I want: <\/p>\n<ul>\n<li>a drop-down list that I can choose from with the stylus<\/li>\n<li>to be able to type in new items not on the list<\/li>\n<li>to be able to type in the start of items on the list, and have auto-complete fill in the rest of the item for me (without needing to open the list)<\/li>\n<\/ul>\n<h4>Why do I want it?<\/h4>\n<p>All of my PDAs have a full QWERTY keyboard. (<em>Look at Treos &#8211; still one of the best examples of interfaces that can be used with one hand<\/em>). Typing the first couple of letters of something is much quicker than getting out your stylus when filling in a form.  If there are 20 words beginning with &#8216;T&#8217;, pressing T then O is a quicker way to pick the item of the list which begins TO. <\/p>\n<h4>How have I done it?<\/h4>\n<p>Badly \ud83d\ude09<\/p>\n<p>It is a bit of a hack&#8230; I created a custom UserControl that contains <strong>both<\/strong> a ComboBox (to give me the drop-down list) with a TextBox directly in front of it (to give me the free-form text editing space). <\/p>\n<p>I made the TextBox slightly narrower than the ComboBox behind it, so that the drop-down arrow is still visible.<\/p>\n<p><img decoding=\"async\" src=\"post-images\/070617-scrnshot.jpg\" alt=\"does this make any more sense?\"\/><\/p>\n<h4>Why this way?<\/h4>\n<p>I needed to be able to show the difference between the bit of the text that has been typed in manually, and the bit of the text that was provided by auto-complete. (Otherwise, the interface is too unclear &#8211; a user wouldn&#8217;t know where their next key-press would go). <\/p>\n<p>This is normally done by highlighting the auto-completed text. For example, after typing &#8220;app&#8221;&#8230;<\/p>\n<p><img decoding=\"async\" src=\"post-images\/070617-scrnshot2.jpg\" alt=\"does this make any more sense?\"\/><\/p>\n<p>TextBox lets you programmatically alter selected text &#8211; with attributes <code>SelectionStart<\/code>, <code>SelectionLength<\/code> and so on. But it doesn&#8217;t have a drop-down list.<br \/>\nComboBox doesn&#8217;t let you programmatically alter selected text &#8211; it doesn&#8217;t have any of the <code>Selection...<\/code> attributes. <\/p>\n<p>So I figured that the quickest and easiest way to get the best of both worlds would be to have both. \ud83d\ude42<\/p>\n<h4>The code<\/h4>\n<p>Fairly straight-forward, but here ya go.<\/p>\n<p><a href=\"http:\/\/dalelane.co.uk\/files\/AutoComplete.zip\">Download it here<\/a>, but here is the interesting bit&#8230;<\/p>\n<pre><code>using System;\r\nusing System.Text;\r\nusing System.Windows.Forms;\r\n\r\nnamespace AutoComplete\r\n{\r\n    \/*\r\n     * AutoComplete\r\n     * \r\n     *   a text box which allows free-form entry of text\r\n     *    whilst also providing auto-complete from a given\r\n     *    list of strings\r\n     * \r\n     *   an item can be selected from the list either by:\r\n     *    a) choosing from the drop-down combobox list\r\n     *    b) typing enough characters of the start of the \r\n     *        desired item to uniquely identify it\r\n     * \r\n     * To use in a form:\r\n     *  1) Drag onto a Form Designer using Visual Studio,\r\n     *      using the Form Designer to resize\/place the \r\n     *      box\r\n     *  2) Use SetItems to provide the list of strings used\r\n     *      to match for auto-complete\r\n     *  3) Use AddItem to add any additional strings\r\n     *\/\r\n    public partial class AutoCompleteTextBox : UserControl\r\n    {\r\n        \/\/ stores the items that are used for auto-complete\r\n        private string[] comboBoxItems = new string[0];\r\n\r\n        public AutoCompleteTextBox()\r\n        {\r\n            InitializeComponent();\r\n\r\n            \/\/ if the user uses the ComboBox to choose an item, \r\n            \/\/  handle this by mirroring it in the TextBox\r\n            innerComboBox.SelectedValueChanged += new EventHandler(innerComboBox_SelectedValueChanged);\r\n\r\n            \/\/ if the user uses the TextBox to enter text, \r\n            \/\/  handle this with our home-made autocomplete code below\r\n            innerTextBox.KeyPress += new KeyPressEventHandler(innerTextBox_KeyPress);\r\n        }\r\n\r\n        \/\/ provide the array of strings to use for auto-complete matches\r\n        \/\/ \r\n        \/\/ it will replace any previous list provided\r\n        public void SetItems(string[] items)\r\n        {\r\n            comboBoxItems = items;\r\n\r\n            Array.Sort(comboBoxItems);\r\n\r\n            innerComboBox.Items.Clear();\r\n\r\n            for (int i = 0; i < comboBoxItems.Length; i++)\r\n            {\r\n                innerComboBox.Items.Add(comboBoxItems[i]);\r\n            }\r\n        }\r\n        \/\/ add an item to the array of strings used for auto-complete matches\r\n        public void AddItem(string newitem)\r\n        {\r\n            string[] newitems = new string[comboBoxItems.Length + 1];\r\n\r\n            Array.Copy(comboBoxItems, newitems, comboBoxItems.Length);\r\n\r\n            newitems[comboBoxItems.Length] = newitem;\r\n\r\n            SetItems(newitems);\r\n        }\r\n        \/\/ clear the list of strings used for auto-complete matches\r\n        public void ClearItems()\r\n        {\r\n            comboBoxItems = new string[0];\r\n            innerComboBox.Items.Clear();\r\n        }\r\n\r\n        \/\/ the user has used the ComboBox to select one of the possible \r\n        \/\/  auto-complete matches\r\n        \/\/\r\n        \/\/ handle this by mirroring it in the textbox\r\n        void innerComboBox_SelectedValueChanged(object sender, EventArgs e)\r\n        {\r\n            innerTextBox.Text = innerComboBox.Text;\r\n        }\r\n\r\n        \/\/ the user has used the TextBox to modify the text\r\n        \/\/  \r\n        \/\/ we look at what has been typed so far, and if it matches \r\n        \/\/  anything in the list, we fill the text box with it\r\n        \/\/ \r\n        \/\/ text which has been 'auto-complete-d' (rather than typed)\r\n        \/\/  is denoted by being selected\r\n        void innerTextBox_KeyPress(object sender, KeyPressEventArgs e)\r\n        {\r\n            \/\/ this is where we store any match found in the auto-complete list\r\n            string match = null;\r\n\r\n            \/\/ what has been typed so far?\r\n            int cursorLocation = innerTextBox.SelectionStart;\r\n            string typedSoFar = innerTextBox.Text.Substring(0, cursorLocation);\r\n            \r\n            \/\/ what is the user adding now?\r\n            switch (e.KeyChar)\r\n            {\r\n                \/\/ BACKSPACE - the user is deleting a character - so we shink\r\n                \/\/   our 'typedSoFar' string by one character\r\n                case (char)Keys.Back:\r\n                    if (cursorLocation > 0)\r\n                    {\r\n                        cursorLocation -= 1;\r\n                        typedSoFar = innerTextBox.Text.Substring(0, cursorLocation);\r\n                    }\r\n                    break;\r\n\r\n                \/\/ DELETE - do nothing, allowing the 'delete' keystroke to delete\r\n                \/\/    the selected text provided from a previous auto-complete\r\n                case (char)Keys.Delete:\r\n                    break;\r\n\r\n                \/\/ RETURN - do nothing - swallow this keystroke\r\n                case (char)Keys.Return:\r\n                    \/\/ don't do anything else\r\n                    goto key_handle_complete;\r\n\r\n                \/\/ OTHERWISE - assume we have a alphanumeric keystroke which \r\n                \/\/   we add to the string typed-so-far\r\n                default:\r\n                    typedSoFar += e.KeyChar;\r\n                    cursorLocation += 1;\r\n                    break;\r\n            }\r\n\r\n            \/\/ look for a match in the auto-complete list\r\n            for (int i = 0; i < comboBoxItems.Length; i++)\r\n            {\r\n                if (comboBoxItems[i].StartsWith(typedSoFar, StringComparison.CurrentCultureIgnoreCase))\r\n                {\r\n                    match = comboBoxItems[i];\r\n\r\n                    \/\/ we want first match - once found, break out\r\n                    break;\r\n                }\r\n            }\r\n\r\n\r\n            \/\/ was a match found?\r\n\r\n            if (match == null)\r\n            {\r\n                \/\/ user has typed something not already in the list\r\n                innerTextBox.Text = typedSoFar;\r\n                innerTextBox.SelectionStart = typedSoFar.Length;\r\n                innerTextBox.SelectionLength = 0;\r\n            }\r\n            else\r\n            {\r\n                \/\/ user has typed text which matches the start of something\r\n                \/\/  in the provided auto-complete list\r\n\r\n                \/\/ display this match, and highlight the portion of it which\r\n                \/\/  was not actually typed by the user\r\n                innerTextBox.Text = match;\r\n                innerTextBox.SelectionStart = cursorLocation;\r\n                innerTextBox.SelectionLength = innerTextBox.Text.Length - innerTextBox.SelectionStart;\r\n\r\n                innerComboBox.SelectedItem = match;\r\n            }\r\n\r\n            \/\/ COMPLETE - finally, prevent key-press being handled by text box itself\r\n            key_handle_complete: e.Handled = true;\r\n        }\r\n    }\r\n}<\/code><\/code><\/pre>\n<h4>Will it work?<\/h4>\n<p>Probably? \ud83d\ude42<\/p>\n<p>Not sure if it will handle a user typing too much too fast, so some sort of synchronisation might need to be looked at before this gets used in earnest. But it is enough for my needs tonight, and seemed to do the trick.<\/p>\n<p>There&#8217;s gotta be a better way though! Any suggestions? <\/p>\n","protected":false},"excerpt":{"rendered":"<p>.NET Compact Framework doesn&#8217;t support auto-complete in text entry controls. I didn&#8217;t realise that until I wanted it for a Form I was throwing together tonight, but there you go. So, in the spirit of Hack Day (which I&#8217;m still gutted to have missed!) I had a quick stab at throwing together something myself. Read [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-166","post","type-post","status-publish","format-standard","hentry","category-code"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/166","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=166"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/166\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}