views:

1040

answers:

4

What would be the best way to develop a text box that remembers the last x number of entries that were put into it. This is a standalone app written with C#.

+6  A: 

This is actually fairly easy, especially in terms of showing the "AutoComplete" part of it. In terms of remembering the last x number of entries, you are just going to have to decide on a particular event (or events) that you consider as an entry being completed and write that entry off to a list... an AutoCompleteStringCollection to be precise.

The TextBox class has the 3 following properties that you will need:

  • AutoCompleteCustomSource
  • AutoCompleteMode
  • AutoCompleteSource

Set AutoCompleteMode to SuggestAppend and AutoCompleteSource to CustomSource.

Then at runtime, every time a new entry is made, use the Add() method of AutoCompleteStringCollection to add that entry to the list (and pop off any old ones if you want). You can actually do this operation directly on the AutoCompleteCustomSource property of the TextBox as long as you've already initialized it.

Now, every time you type in the TextBox it will suggest previous entries :)

See this article for a more complete example: http://www.c-sharpcorner.com/UploadFile/mahesh/AutoCompletion02012006113508AM/AutoCompletion.aspx

AutoComplete also has some built in features like FileSystem and URLs (though it only does stuff that was typed into IE...)

Adam Haile
A: 

Adam Haile,

Do you just write that out to file and then import when you want to save/load when the program starts?

Ethan Gunderson
This is not an answer. Please use a comment or edit your question.
Geoffrey Chetwood
+3  A: 

@Ethan

I forgot about the fact that you would want to save that so it wasn't a per session only thing :P But yes, you are completely correct.

This is easily done, especially since it's just basic strings, just write out the contents of AutoCompleteCustomSource from the TextBox to a text file, on separate lines.

I had a few minutes, so I wrote up a complete code example...I would've before as I always try to show code, but didn't have time. Anyway, here's the whole thing (minus the designer code).

namespace AutoComplete
{
    public partial class Main : Form
    {
        //so you don't have to address "txtMain.AutoCompleteCustomSource" every time
        AutoCompleteStringCollection acsc;
        public Main()
        {
            InitializeComponent();

            //Set to use a Custom source
            txtMain.AutoCompleteSource = AutoCompleteSource.CustomSource;
            //Set to show drop down *and* append current suggestion to end
            txtMain.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            //Init string collection.
            acsc = new AutoCompleteStringCollection();
            //Set txtMain's AutoComplete Source to acsc
            txtMain.AutoCompleteCustomSource = acsc;
        }

        private void txtMain_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                //Only keep 10 AutoComplete strings
                if (acsc.Count < 10)
                {
                    //Add to collection
                    acsc.Add(txtMain.Text);
                }
                else
                {
                    //remove oldest
                    acsc.RemoveAt(0); 
                    //Add to collection
                    acsc.Add(txtMain.Text);
                }
            }
        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            //open stream to AutoComplete save file
            StreamWriter sw = new StreamWriter("AutoComplete.acs");

            //Write AutoCompleteStringCollection to stream
            foreach (string s in acsc)
                sw.WriteLine(s);

            //Flush to file
            sw.Flush();

            //Clean up
            sw.Close();
            sw.Dispose();
        }

        private void Main_Load(object sender, EventArgs e)
        {
            //open stream to AutoComplete save file
            StreamReader sr = new StreamReader("AutoComplete.acs");

            //initial read
            string line = sr.ReadLine();
            //loop until end
            while (line != null)
            {
                //add to AutoCompleteStringCollection
                acsc.Add(line);
                //read again
                line = sr.ReadLine();
            }

            //Clean up
            sr.Close();
            sr.Dispose();
        }
    }
}

This code will work exactly as is, you just need to create the GUI with a TextBox named txtMain and hook up the KeyDown, Closed and Load events to the TextBox and Main form.

Also note that, for this example and to make it simple, I just chose to detect the Enter key being pressed as my trigger to save the string to the collection. There is probably more/different events that would be better, depending on your needs.

Also, the model used for populating the collection is not very "smart." It simply deletes the oldest string when the collection gets to the limit of 10. This is likely not ideal, but works for the example. You would probably want some sort of rating system (especially if you really want it to be Google-ish)

A final note, the suggestions will actually show up in the order they are in the collection. If for some reason you want them to show up differently, just sort the list however you like.

Hope that helps!

Adam Haile
+1  A: 

I store the completion list in the registry.

The code I use is below. It's reusable, in three steps:

  1. replace the namespace and classname in this code with whatever you use.
  2. Call the FillFormFromRegistry() on the Form's Load event, and call SaveFormToRegistry on the Closing event.
  3. compile this into your project.

You need to decorate the assembly with two attributes: [assembly: AssemblyProduct("...")] and [assembly: AssemblyCompany("...")] . (These attributes are normally set automatically in projects created within Visual Studio, so I don't count this as a step.)

Managing state this way is totally automatic and transparent to the user.

You can use the same pattern to store any sort of state for your WPF or WinForms app. Like state of textboxes, checkboxes, dropdowns. Also you can store/restore the size of the window - really handy - the next time the user runs the app, it opens in the same place, and with the same size, as when they closed it. You can store the number of times an app has been run. Lots of possibilities.

namespace Ionic.ExampleCode
{
    public partial class NameOfYourForm
    {
        private void SaveFormToRegistry()
        {
            if (AppCuKey != null)
            {
                // the completion list
                var converted = _completions.ToList().ConvertAll(x => x.XmlEscapeIexcl());
                string completionString = String.Join("¡", converted.ToArray());
                AppCuKey.SetValue(_rvn_Completions, completionString);
            }
        }

        private void FillFormFromRegistry()
        {
            if (!stateLoaded)
            {
                if (AppCuKey != null)
                {
                    // get the MRU list of .... whatever
                    _completions = new System.Windows.Forms.AutoCompleteStringCollection();
                    string c = (string)AppCuKey.GetValue(_rvn_Completions, "");
                    if (!String.IsNullOrEmpty(c))
                    {
                        string[] items = c.Split('¡');
                        if (items != null && items.Length > 0)
                        {
                            //_completions.AddRange(items);
                            foreach (string item in items)
                                _completions.Add(item.XmlUnescapeIexcl());
                        }
                    }

                    // Can also store/retrieve items in the registry for
                    //   - textbox contents
                    //   - checkbox state
                    //   - splitter state
                    //   - and so on
                    //
                    stateLoaded = true;
                }
            }
        }

        private Microsoft.Win32.RegistryKey AppCuKey
        {
            get
            {
                if (_appCuKey == null)
                {
                    _appCuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(AppRegistryPath, true);
                    if (_appCuKey == null)
                        _appCuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(AppRegistryPath);
                }
                return _appCuKey;
            }
            set { _appCuKey = null; }
        }

        private string _appRegistryPath;
        private string AppRegistryPath
        {
            get
            {
                if (_appRegistryPath == null)
                {
                    // Use a registry path that depends on the assembly attributes,
                    // that are presumed to be elsewhere. Example:
                    // 
                    //   [assembly: AssemblyCompany("Dino Chiesa")]
                    //   [assembly: AssemblyProduct("XPathVisualizer")]

                    var a = System.Reflection.Assembly.GetExecutingAssembly();
                    object[] attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyProductAttribute), true);
                    var p = attr[0] as System.Reflection.AssemblyProductAttribute;
                    attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyCompanyAttribute), true);
                    var c = attr[0] as System.Reflection.AssemblyCompanyAttribute;

                    _appRegistryPath = String.Format("Software\\{0}\\{1}",
                                                     p.Product, c.Company);
                }
                return _appRegistryPath;
            }
        }

        private Microsoft.Win32.RegistryKey _appCuKey;
        private string _rvn_Completions = "Completions";
        private readonly int _MaxMruListSize = 14;
        private System.Windows.Forms.AutoCompleteStringCollection _completions;
        private bool stateLoaded;
    }

    public static class Extensions
    {
        public static string XmlEscapeIexcl(this String s)
        {
            while (s.Contains("¡"))
            {
                s = s.Replace("¡", "&#161;");
            }
            return s;
        }
        public static string XmlUnescapeIexcl(this String s)
        {
            while (s.Contains("&#161;"))
            {
                s = s.Replace("&#161;", "¡");
            }
            return s;
        }

        public static List<String> ToList(this System.Windows.Forms.AutoCompleteStringCollection coll)
        {
            var list = new List<String>();
            foreach (string  item in coll)
            {
                list.Add(item);
            }
            return list;
        }
    }
}

Some people shy away from using the Registry for storing state, but I find it's really easy and convenient. If you like, You can very easily build an installer that removes all the registry keys on uninstall.

Cheeso