Microsoft Developer Network > Forenhomepage > Windows Forms General > Auto complete Text box for setting Tags (c#)
Stellen Sie eine FrageStellen Sie eine Frage
 

Vorgeschlagene AntwortAuto complete Text box for setting Tags (c#)

  • Montag, 6. Juli 2009 08:10alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Hi,
    In my project I need to catalogue picture with tags.
    I built an interface that opens the picture and then you need to insert your tags according to it's content.
    I have something like 250 keywords in a data base, and I want them to appear as the user enter text. so II used the textBox auto completion custom source and added all the words to it.
    Everything is right with the first word. for example: if I have apple in my data base, and I start typing ap so apple comes in the auto completion list and I can choose it.
    the problem start here, because I want to tag the picture - more than one word is needed, but because I after I press the spacebar( after the apple for example) the suggestion doesn't come again.
    What I want to do is, that after every stroke of the spacebar the list will reset it self and will show all the words like it was the first word.
    I'm using .Net 3.5 under visual 2008 environment.
    Thank you very much for your help
    Alon
    for example: if I have apple and orange in the database , I want that if I press ap it will suggest the apple, I will choose it, press spacebar and the type or it will suggest orange.


Alle Antworten

  • Mittwoch, 8. Juli 2009 11:07Bruce.ZhouMSFT, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Hi alon,

    Can you tell me which kind of application you are trying to build now? Windows Forms application or WPF application? It seems there's no AutoCompleteCustomSource for TextBox control in WPF.

    Best regards,
    Bruce Zhou
    Please mark the replies as answers if they help and unmark if they don't.
  • Mittwoch, 8. Juli 2009 17:35alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    hmmm Windows Forma application...
    does it matter?
  • Mittwoch, 8. Juli 2009 18:03David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    It does matter, WPF, ASP, or Windows are completely separate technologies.  You're posting in a WPF forum, which is, as I said, completely different from WinForms.  You ought to be posting in the WinForms forum.  Hopefully a moderator will move this to that forum.

    That being said, you might want to try a little different approach.  Use a ListBox or ComboBox instead.  You won't be able to accomplish what you're trying to accomplish using a TextBox, because the very nature of a TextBox is to allow the user to enter whatever text he/she wishes.  They could enter "Happy Hour" into the TextBox for all the TextBox cares.  It doesn't matter.  There's no possible restriction in a TextBox, so the TextBox is simply the wrong control to use. 

    Check the following link for an implementation that will work with either a ComboBox or a ListBox, and edit the example to your liking. 

    http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e8de845f-fd81-4e09-8f27-2c8562a687d1/
    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Mittwoch, 8. Juli 2009 18:07alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    10x for your reply.
    I didn't say I want to force something in the TextBox, I just want to give the user options for completion , same like google suggest which suggest you the continue of your sentence.
  • Mittwoch, 8. Juli 2009 18:13David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    You're absolutely right.  I misread your question. 

    This can't be done natively with the TextBox.  You'll need to write a custom control to solve this issue.

    Google's mechanism works off of an incredibly large database of suggestions.  If you'll notice, the autocomplete fills in things automatically and contextually to what the previous statement is. The reason for this is that Google has their server do a query on the top searches starting with what you've already written, and it displays a small subset of those on the screen.  Example: try typing "britney" into the search box, you'll see tons of hits that all start with "britney spears".  The only way to get this functionality is to have an equally large database of suggestions.

    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Mittwoch, 8. Juli 2009 18:48alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    O.k I'm not google(yet anyway), but as I said in my question - I'm not looking to be google in the moment. all I want is the ability to suggest the user keywords as he type in.
    the keywords will come from my data base that is already filled with keywords.
    At the moment I solve it for the second word by inserting manually(with a loop inside a loop)to the AutoCompleteCustomSource all the combinations of a pairs of 2 words.
    for example:
    if I have three keywords in my list:dog,cat,bag - I will insert(again manually): dog, dog cat ,dog bag , cat , cat dog , cat bag , bag , bag dog , bag cat - this way I covered all the combinations(of only 2 keywords) available for those words.
    but what if the user want to insert 3 keywords? if I want  to cover that I'll need a loop inside a loop inside a a loop... and what if I have 250 keywords in my DB and I want to cover all the combinations available??... I think u got it :-).
    So I'm looking into a more elegant way like I stated in my question...
    If anyone has a suggestion I will be happy to receive it.
    10x
    Alon
  • Mittwoch, 8. Juli 2009 19:43David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Yeah, this isn't a happy subject.  A quick search on google for AccessViolation AutoCompleteCustomSource turns up plenty of buggy responses.  If you create a custom control, you could dynamically change the values whenever a space is added on a textchanged event, appending all the different combinations to the list.  There's a workaround to the AccessViolation issue, but it's unmanaged, and it'll probably be simpler to create a custom control. 

    Sorry for the bad news, but apparently that's the way it is.
    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Mittwoch, 8. Juli 2009 20:17alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Yeah I guess...
    Any suggestions where to start?
  • Mittwoch, 8. Juli 2009 20:23David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     Vorgeschlagene Antwort
    I'm not sure what your level of expertise is, but you might want to try to use a textbox in conjunction with a listbox possibly, and expand the listbox to a particular size when the textbox is focused, and collapse it when the textbox is closed.  Just an idea, but I think there's a million ways to skin this cat.  I'd start on the MSDN page about creating a custom user control.
    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Mittwoch, 8. Juli 2009 20:26alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    O.k 10x.
    I think I won't mark it as an answer cause maybe someone will know how to do it. But thanks a lot for your effort - your direction into AccessViolation really helps
  • Mittwoch, 8. Juli 2009 22:03David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     Enthält Code
    Okay, fine.  :)

    Here's some code for you.  I've ported this from sheng's website I linked to above (from the "workaround" link).  Add a new file in your application, and put this code in it:

    using WindowsFormsApplication1;
    using System;
    using System.Runtime.InteropServices.ComTypes;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Windows.Forms;
    using System.ComponentModel;
    
    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("EAC04BC0-3791-11d2-BB95-0060977B464C")]
    [SuppressUnmanagedCodeSecurity]
    public interface IAutoComplete2
    {
        int Init([In] HandleRef hwndEdit, [In] IEnumString punkACL, [In] string pwszRegKeyPath, [In] string pwszQuickComplete);
        void Enable([In] bool fEnable);
        int SetOptions([In] int dwFlag);
        void GetOptions([Out] IntPtr pdwFlag);
    
    }
    
    
    internal class CustomSource : BindingSource, IEnumString
    {
        // Fields
        private static Guid autoCompleteClsid = new Guid("{00BB2763-6A77-11D0-A535-00C04FD7D062}");
        private IAutoComplete2 autoCompleteObject2;
        private int current;
        private int size;
        private string[] strings;
    
        public void Bind(TextBox textBox)
        {
            this.autoCompleteObject2.SetOptions((int)textBox.AutoCompleteMode);
            this.autoCompleteObject2.Init(new HandleRef(textBox, textBox.Handle),
                this, string.Empty, string.Empty);
        }
    
        // Methods
        public CustomSource(string[] strings)
        {
            Array.Clear(strings, 0, this.size);
            if (strings != null)
            {
                this.strings = strings;
            }
            this.current = 0;
            this.size = (strings == null) ? 0 : strings.Length;
            Guid gUID = typeof(IAutoComplete2).GUID;
            object obj2 = Activator.CreateInstance(Type.GetTypeFromCLSID(autoCompleteClsid));
            this.autoCompleteObject2 = (IAutoComplete2)obj2;
        }
    
        public bool Bind(HandleRef edit, int options)
        {
            bool flag = false;
            if (this.autoCompleteObject2 == null)
            {
                return flag;
            }
            try
            {
                this.autoCompleteObject2.SetOptions(options);
                this.autoCompleteObject2.Init(edit, this, null, null);
                return true;
            }
            catch
            {
                return false;
            }
        }
    
        public void RefreshList(string[] newSource)
        {
            Array.Clear(this.strings, 0, this.size);
            if (this.strings != null)
            {
                this.strings = newSource;
            }
            this.current = 0;
            this.size = (this.strings == null) ? 0 : this.strings.Length;
        }
    
        public void ReleaseAutoComplete()
        {
            if (this.autoCompleteObject2 != null)
            {
                Marshal.ReleaseComObject(this.autoCompleteObject2);
                this.autoCompleteObject2 = null;
            }
        }
    
        void IEnumString.Clone(out IEnumString ppenum)
        {
            ppenum = new CustomSource(this.strings);
        }
    
        public string DisplayMember { get; set; }
    
        int IEnumString.Next(int celt, string[] rgelt, IntPtr pceltFetched)
        {
            if (celt < 0)
            {
                return -2147024809;
            }
    
            int index = 0;
    
            while ((this.current < this.size) && (celt > 0))
            {
                object item  = this.strings[this.current];
                bool useDisplayMember = false;
                if (string.IsNullOrEmpty(DisplayMember))
                {
                    ICustomTypeDescriptor customTypeDescriptor = item as CustomTypeDescriptor;
                    if (customTypeDescriptor != null)
                    {
                        PropertyDescriptorCollection descriptorCollection =
                            customTypeDescriptor.GetProperties();
                        if (descriptorCollection != null)
                        {
                            PropertyDescriptor propertyDescriptor = descriptorCollection[DisplayMember];
                            if (propertyDescriptor != null)
                            {
                                rgelt[index] = propertyDescriptor.GetValue(item).ToString();
                                useDisplayMember = true;
                            }
                        }
                    }
    
                    if (!useDisplayMember)
                    {
                        if (item != null)
                            rgelt[index] = item.ToString();
                    }
                }
    
                current++;
                index++;
                celt--;
            }
    
            if (pceltFetched != IntPtr.Zero)
            {
                Marshal.WriteInt32(pceltFetched, index);
            }
            if (celt != 0)
            {
                return 1;
            }
    
            return 0;
        }
    
        void IEnumString.Reset()
        {
            this.current = 0;
        }
    
        int IEnumString.Skip(int celt)
        {
            this.current += celt;
            if (this.current >= this.size)
            {
                return 1;
            }
            return 0;
        }
    }
    
    
    
    This code is the custom auto-complete source that the TextBox is going to use to bind it's information.  It's fixed so that the AccessViolationException should not occur. 

    Here's a little example I whipped up on how to use this.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Runtime.InteropServices.ComTypes;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            // here are my suggestions.  You can do whatever you want here. 
            string[] suggestions = { "Dog", "Cat", "Box" };
    
            string lastText = string.Empty;
            static bool textChanging = false;
            private CustomSource autoComplete;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
    
                autoComplete = new CustomSource(suggestions);
                textBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
                textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
                autoComplete.Bind(textBox1);
            }
    
            private void textBox1_TextChanged(object sender, EventArgs e)
            {
                if (!textChanging)
                {
                    textChanging = true;
                    string prefix = "";
                    string text = textBox1.Text;
                    bool changed = false;
    
                    if (lastText.Length < text.Length && text.EndsWith(" "))
                    {
                        prefix = text;
                        changed = true;
                    }
                    else if (lastText.Length < text.Length && lastText.EndsWith(" ") && text.Contains(' '))
                    {
                        prefix = text.Substring(0, text.LastIndexOf(' '));
                        changed = true;
                    }
    
                    if (changed)
                    {
                        autoComplete.ReleaseAutoComplete();
                        autoComplete = new CustomSource(suggestions.Select(t => prefix + t).ToArray());
                        autoComplete.Bind(textBox1);
                    }
    
                    textChanging = false;
                }
            }
        }
    }
    
    



    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Donnerstag, 9. Juli 2009 03:03Bruce.ZhouMSFT, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    I am going to move this thread to the Windows Forms Forum.

    Best regards,
    Bruce Zhou



    Please mark the replies as answers if they help and unmark if they don't.
  • Donnerstag, 9. Juli 2009 03:10Bruce.ZhouMSFT, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Hi alon,

    Based on my experience, I agree with David, creating a custom user control with a TextBox and a ListBox is the easy way to go.

    Best regards,
    Bruce Zhou
    Please mark the replies as answers if they help and unmark if they don't.
  • Donnerstag, 9. Juli 2009 06:59alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    wow!!!
    way out of my league(especially what Sheng wrote in his website!
    I will give it a try and tell you what happened ;-)
    thank you very much
  • Freitag, 10. Juli 2009 11:37alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    O.k it works awesome but I have a question:
    with that code every time I press the space bar the list of words regenerates by taking whats in the text box and adding to it all the other words, and then you can see in the list what you already enter+all the words you entered to suggestion.
    for example:if I already entered dog and pressed space bar the options I'll see is:dog dog, dog cat , dog box.
    so my question is: is it possible to show in the list only the new words without what I entered  far? if I'll follow my example,if I entered dog what I want to be in the list is:dog,cat,box.
    In a matter of fact - I want the list to be constant - I always want the words that I entered manually(of course I still want the auto completion).
    Is it possible? am I clear enough ? :-)
  • Freitag, 10. Juli 2009 11:55David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     Enthält Code
    You can definately get this functionality.  Instead of:

    autoComplete = new CustomSource(suggestions.Select(t => prefix + t).ToArray());

    Perhaps you could try:

    autoComplete = new CustomSource(suggestions.Where(t => !prefix.Split().Contains(t)).Select(t => prefix + t).ToArray());

    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Sonntag, 12. Juli 2009 06:24alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Thank you.
    I will give it a try and let you know
  • Montag, 13. Juli 2009 08:19alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    It seems both lines act the same...
    any idea?
  • Montag, 13. Juli 2009 10:56David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    It seems both lines act the same...
    any idea?

    Please be more specific.  Also, it sounds like your original question has been answered.  If this is the case, please mark this post as answered, and start a new thread. 
    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Montag, 13. Juli 2009 11:18alon leshem TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     Enthält Code
    I asked if there is a way not to show what I entered so far in the suggestions , but only what I can add(according to pre defined list).
    you said :
    You can definately get this functionality.  Instead of:

    autoComplete = new
     CustomSource(suggestions.Select(t => prefix + t).ToArray());

    Perhaps you could try:

    autoComplete = new CustomSource(suggestions.Where(t => !prefix.Split().Contains(t)).Select(t => prefix + t).ToArray());

    so I tried it and both lines act the same way- the suggestions contains what I entered so far... that;s what I meant by :
    "It seems both lines act the same...
    any idea?"
  • Montag, 13. Juli 2009 11:37David M MortonMVP, ModeratorTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     
    Don't be helpless.  Use your debugger.  Pass into the constructor an IEnumerable-Derived collection of strings that contains the items you want to be considered as suggestions for the textbox.  How you get to that list of strings is up to you. 

    Take it one step at a time.  What do you want, and how would you do it by hand?

    1. You would look at the words the user has typed in.
    2. You would look at the available words for the user.
    3. You would remove the words the user has typed in from the list of available words. 
    4. You would add what the user has already typed in to the front of the list resulting from step 3.
    5. You would bind that list to the textbox, using the CustomSource constructor, and the Bind command. 

    So for 1:

    1. Get a list of the words the user has typed in.  Use the Split() method for this.
    2. Copy a list of the available words for the user.  (use the List<T> constructor, or call .ToList()). Remember, you want to keep the existing items.
    3. You compare the result of 1 with the result of 2.  Remove the already existing words from number 2. Remember, strings are case-sensitive, so you may need to call .ToUpper before comparing.
    4. Go through each of the items in the list, and add in the user's existing entry to the front of each string.
    5. Release the previous list, create a new one, and bind it to the text box. 

    Break it down into steps, and if it doesn't work, break it down more, and use the debugger to see exactly what's happening.
    David Morton - http://blog.davemorton.net/ - @davidmmorton - ForumsBrowser, a WPF MSDN Forums Client
  • Dienstag, 14. Juli 2009 21:54Alex Franke TeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillenTeilnehmermedaillen
     Vorgeschlagene Antwort
    I had the same problem. In my case, users wanted to "tag" business objects with either their own tag, or choose from set of previously used tags. Tags could include spaces or just about any other characters. If a "tag" was already typed in, then it would no longer be one of the auto-complete options. Auto-complete options would not include typed-in tags. Users should be able to switch to the next value using arrow key or tab/shift-tab. Capitalization should be corrected when the suggestion is accepted.

    Later, users wanted to switch to the next option by using the comma, so I added that as well. 

    To accomplish this, I used a FlowLayoutPanel as the base control and added auto-suggest textboxes as needed to provide auto-suggest functionality.

    It's not perfect yet, but it works fine for me. The code over here: http://www.codecreations.com/site/blog/42-cc-blog/118-multi-input-autocomplete-textbox

    • Als Antwort vorgeschlagenAlex Franke Dienstag, 14. Juli 2009 21:59
    • BearbeitetAlex Franke Dienstag, 14. Juli 2009 21:55hyperlinked url
    •