locked
Dynamic Filling of TextBox AutoComplete not working RRS feed

  • Question

  • Hi guys,

     

    i would like to fill the textbox autocomplete stringcollection "on the fly" while typing. i don't want to fill the collection while initializing, 'cause it's too much data: the sql server-table has about 100.000+ rows. the filling takes too much time and the "autocomplete-popup-find-something-box" itself is getting too slow, too.



    my configuration:
    .NET-Framework: 2.0.50727, System.Windows.Forms.TextBox

    TextBox-Properties: AutoCompleteSource: CustomSource; AutoCompleteMode: Suggest



    i tried several different things...


    1. i did put this code in the “TextChanged”-event, but i think it's 'too late' there:

    private void textTiteUltTest_TextChanged(object sender, EventArgs e) {

     

      TextBox tb = (TextBox)sender;

      DataLayer DBClass = new DataLayer();

     

      try {

     

        string SQL = "select TITE_TITLES from TITE where TITE_TITLES like @strVar order by TITE_TITLES";

        string strVar = textTiteUltTest.Text + "%";

     

        //Get SqlDataReader from DataClass

        SqlDataReader sqlDR = DBClass.ReturnDataReader(SQL, "@strVar", strVar);

        AutoCompleteStringCollection autoCol = new AutoCompleteStringCollection();

     

        //fill stringcollection

        while (sqlDR.Read()) {

          autoCol.Add(sqlDR["TITE_TITLES"].ToString());

        }

     

        //fill autocomplete textbox

        lock (tb.AutoCompleteCustomSource.SyncRoot) {

          tb.AutoCompleteCustomSource = autoCol;

        }

      }

     

      catch (Exception exc) {

        Console.WriteLine(exc.Data.ToString() + exc.Message.ToString());

      }

     

      finally {

        //cleanup

        DBClass.Dispose();

        Console.WriteLine("'textTiteUltTest_TextChanged'");

      }

     

    }


    error -> AccessViolationException:
    "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
    Data: {System.Collections.ListDictionaryInternal}


    not in the try/catch-block, but here:

        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }


    So i think, while i'm typing into the textbox the code from TextChangded-event is filling the autocomplete-stringcollection in one thread and the internal textbox-method/function needs to read from the autocomplete-stringcollection at the same time in a different thread.

    The locking of SyncRoot doesn’t help at all, but I’m not even sure if I made it right in this case.

    Also i have to say that this code sometimes works and sometimes it crashes. i think it depends on the speed of getting the sql server resultset.

     


    2. then i tried the same code with some small changes in the “PreviewKeyDown”-event

    private void textTiteUltTest_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) {

     

     

      TextBox tb = (TextBox)sender;

      DataLayer DBClass = new DataLayer();

     

      try {

     

        string SQL = "select TITE_TITLES from TITE where TITE_TITLES like @strVar order by TITE_TITLES";

        string strVar = e.KeyCode.ToString() + "%"// textTiteUltTest.Text + "%";

     

        //Get SqlDataReader from DataClass

        SqlDataReader sqlDR = DBClass.ReturnDataReader(SQL, "@strVar", strVar);

        AutoCompleteStringCollection autoCol = new AutoCompleteStringCollection();

     

        //fill stringcollection

        while (sqlDR.Read()) {

          autoCol.Add(sqlDR["TITE_TITLES"].ToString());

        }

     

        //fill autocomplete textbox

        lock (tb.AutoCompleteCustomSource.SyncRoot) {

          tb.AutoCompleteCustomSource = autoCol;

        }

     

      }

     

      catch (Exception exc) {

        Console.WriteLine(exc.Data.ToString() + exc.Message.ToString());

      }

     

      finally {

        //cleanup

        DBClass.Dispose();

        Console.WriteLine("'textTiteUltTest_PreviewKeyDown'");

      }

     

    }

     

     

    Now nothing happens. The funny thing is that it jumps only into the “PreviewKeyDown” and the “KeyUp”-event. So the KeyDown, KeyPress and TextChanged-events are suppressed at all and you don't even see the letter you typed. I don't need these events in this case, but maybe that's the problem?



    3., 4. and 5. i tried the same code with some small changes in the “KeyUp”, “KeyPress” and “KeyDown”-event, but this didn't make it, too..


    who knows, maybe it's a bug and somebody knows a workaround - or i'm doing something completely wrong....

     

    So I hope one of the MS-windows-form-control-gurus can help me with my "problem".   ;-)

     

     

    thx in advance!

    Daniel

    Cologne/Germany

    Monday, February 27, 2006 12:27 AM

Answers

  • I think I found the problem.

    Go to the Project Property page, and then go into the Security Section.

    I had "Enable ClickOnce Security Settings" checked.  Once I unchecked this and ran my application, this problem went away.

     

     

    Monday, December 4, 2006 4:14 PM

All replies

  • mmmhhh..  it's me again. i'm still working on that problem since. but i didn't find any solution. i made my code easier to understand and to reproduce for you guys, so you can copy & paste that into your development studio.

    what you need/what i use:

     

    .NET-framework 2.0, Windows.Forms

    just take a new form, put a button and a textbox on it, change textbox-properties to:

    AutoCompleteMode: Suggest
    AutoCompleteSource: CustomSource

    and take the following code:


    random-code taken and changed from the asp.net-atlas-project:

    http://atlas.asp.net/quickstart/atlas/doc/controls/default.aspx
    http://atlas.asp.net/quickstart/atlas/samples/controls/servercontrols1.aspx
    http://atlas.asp.net/quickstart/util/srcview.aspx?path=~/atlas/samples/controls/servercontrols1.src&file=FilterService.asmx&lang=C%23+Source

    anyways...



    //this method generates AutoCompleteStringCollection with 100 random entries

    public AutoCompleteStringCollection GetFilteredList(string prefixText) {

     

      AutoCompleteStringCollection AutoCol = new AutoCompleteStringCollection();

      Random random = new Random((int)DateTime.Now.Ticks);

      for (int i = 0; i < 100; i++) {

        char c1 = (char)random.Next(33, 127);

        char c2 = (char)random.Next(33, 127);

        char c3 = (char)random.Next(33, 127);

        AutoCol.Add(prefixText + c1 + c2 + c3);

      }

      return AutoCol;

    }


    private void button1_Click(object sender, EventArgs e) {

     

      //fill autocomplete textbox

      lock (textBox1.AutoCompleteCustomSource.SyncRoot) {

        textBox1.AutoCompleteCustomSource = GetFilteredList("A");

      }

     

    }


    after compiling and starting you can press the button and type something in the textbox what begings with an "A". that works fine...


    the main question is:

    how can you fill/update a textbox autocompletestringcollection while you typing?


    in the textchanged-event the code would look something like this:


    private void textBox1_TextChanged(object sender, EventArgs e) {

     

      //fill autocomplete textbox..

      //SyncRoot-object is not locked at all, so it looks like that is not important

      lock (textBox1.AutoCompleteCustomSource.SyncRoot) {

        textBox1.AutoCompleteCustomSource = GetFilteredList(textBox1.Text);

      }

     

    }



    and that kind of works. it "only" crashes sometimes, and also bad: the first letter you type does nothing. i tried also all keyevents (previewkeydown, keydown, keypress, keyup) with several different code-snippets including sendkeys.send & sendkeys.sendwait. nothing works.

    i hope somebody has/had the same problem and found a solution for this. i want it to work like google-suggest or this sample from the .net-atlas-project ( http://atlas.asp.net/quickstart/atlas/samples/controls/servercontrols1.aspx), but local on the desktop with windows-forms.


    thx again
    Daniel

     

    ps: this "random generating code" is only to show the problem, the real data comes from a sql server database as i said in the first post.

    Thursday, March 2, 2006 5:21 PM
  • I'm struggling with exactly the same problem. I need to change the autocomplete list on the fly (because it depends on user input) and when I use Suggest, the whole thing gets crashy. As if clearing the AutoCompleteCustomSource messes up the dropdown list.

    The error I get is indeed the memory access error. If anyone has found a solution (any solution! despair, despair!), please post it.

    Sjoerd Cranen.

    [edit]
    I tried using Append instead, but it behaves strangely. It sometimes just appends text instead of "suggesting" it, and there is no way nót to append the text when leaving the textbox. Furthermore, the cursor position is reset at random times to point to the beginning of the text.
    Thursday, March 23, 2006 10:15 AM
  • hello,

    I was just wondering if you've found a workaround for this bug, cause I'm facing exactly the same problem here.

    Regards,

    H. Eskandari

    Wednesday, August 16, 2006 3:23 PM
  • Two things:

    1) Ignore(catch and consume) the AccessViolationException // I did the same thing in my code and it works fine..

    2) The code is to be written in the TextChangedEvent itself. You might want to incremental-'cache' the data in memory, if you do not want to hit Database every time (or) call database only if the user types atleast 3 or 4 or 5 characters in the textbox since calling database for *every* textchanged event will take the application's performance for a huge toss!!!..(try indexing the table against which your query is being run etc. so that the results may be a bit faster..) 

    I dont think theres any bug per-se in the autocomplete textbox stuff..

    best

    sriram

     

    Wednesday, August 16, 2006 3:36 PM
  • I would regard the generation of access violation exceptions when running a completely normal event handler as a bug. Furthermore I don't like my cursor position moving around when I haven't given orders for it to do so. And when suggestions are automatically appended as you type, that's just plain annoying.

    Ignoring the errors is indeed an easy way out, but it doesn't feel very comfortable. Also, it doesn't solve the dodgy cursor position problem when using the Append method.
    Wednesday, August 16, 2006 7:28 PM
  • So where are the Microsoft gurus? None of you read this thread soince February? Guys!!!!!!
    Monday, October 2, 2006 3:14 PM
  • I got around this by creating a UserControl that contains a textbox and a datagrid (created at runtime - add it to the parent form's controls so it doesn't get clipped). After a delay in typing it fires a "need autocomplete data" event. When you fill the datasource it drops down below the textbox. Trapped the arrow up/down keys in the textbox to navigate through the datagrid.

    Not a perfect solution but it did the job. I'm no guru but it was simple enough to do.
    Tuesday, October 3, 2006 6:33 AM
  • Hi guys,

    I just write one name and one link to help you:

    XtraGrid Solution for Visual Studio 2005

    http://www.devexpress.com

     

    It is the perfect solution for your needs, and it comes also with sourcecode if needed. Our company has already bought this for complex developments and it can be redistributed with the application, can be changed or localized.

    It is a great solution, I think!

    I know that this is not the direct solution of your programming problem, but it is an essential help for the solution of such future problems and needs.

    Best regards,

    Gyula Csiak-Sedivy

    http://www.mbsoft.hu

    Sunday, November 5, 2006 8:40 AM
  • Hi, I have the same problem, the solution I found is to use AddRange Method on AutoCompleteCustomSource.
    Wednesday, November 8, 2006 2:28 PM
  • Hello friends,

    if you are using "AutoCompleteCustomSource.Clear()" method , Please remove that and try.

    We also had the same problem , and found working when we commented the call to clear() method.

    Thank you

    Tuesday, November 28, 2006 9:30 AM
  • I was having a problem with an AccessViolationException everytime I changed the AutoCompleteSource on a combo. I fixed it by setting the AutoCompleteMode to AutoCompleteMode.None before changing the AutoCompleteSource and then setting it back after I had changed the Source. I haven't had a problem since. hope that helps.

    Dim iACM As Windows.Forms.AutoCompleteMode
    iACM = cbo.AutoCompleteMode
    cbo.AutoCompleteMode = AutoCompleteMode.None
    cbo.AutoCompleteSource = iNewSrc
    cbo.AutoCompleteMode = iACM
    • Proposed as answer by ajhuddymsdn Friday, October 9, 2009 10:00 PM
    Thursday, November 30, 2006 12:05 PM
  • Glad to see that I'm not the only one having this problem. I'm using a textbox with a custom autocomplete source.  We use it to search for customers based on last name, and show matches as they type. I got my autocomplete working.  Initially had a problem using the Up and Down arrow keys, since this fired the TextChanged events and you couldn't select an entry that way.  To get around that, I used the following code snippet:

    Private Sub tbxLastName_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.PreviewKeyDownEventArgs) Handles tbxLastName.PreviewKeyDown
        'Catch up and down arrows, and don't change text box if these keys are pressed.
        If e.KeyCode = Keys.Up Or e.KeyCode = Keys.Down Then
            blnUpdateText = False
        Else
            blnUpdateText = True
        End If
    End Sub

    Private Sub tbxLastName_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbxLastName.TextChanged
        If blnUpdateText Then
            ' System.Collections.Specialized.StringCollection
            custList = New System.Windows.Forms.AutoCompleteStringCollection()
            For Each val As String In dsOrion.LookupLastName(Me.tbxLastName.Text) ' This returns a String array.
                custList.Add(val)
            Next
            If custList.Count > 0 Then
                Me.tbxLastName.AutoCompleteCustomSource = custList
            End If
        End If
    End Sub

    Now the weird thing is... when you type in two characters into this textbox from the time the program starts up, it tries to autocomplete, but get the same error as everyone else has above... however... I also have a Search button that when you click on it, it will give you an unfiltered list if the last name box is empty.  If I click on the Search button first and get a list of results, the text box no longer has a problem autocompleting.  So it appears that I only have problems when Autocompleting the textbox is my first operation.  Also, it seems to be some type of COM related problem.  Maybe the text box  suggestions are COM-based?

    Sunday, December 3, 2006 6:10 AM
  •  Makoto wrote:

    Also, it seems to be some type of COM related problem. Maybe the text box suggestions are COM-based?




    i think so too...
    Sunday, December 3, 2006 11:29 PM
  • I think I found the problem.

    Go to the Project Property page, and then go into the Security Section.

    I had "Enable ClickOnce Security Settings" checked.  Once I unchecked this and ran my application, this problem went away.

     

     

    Monday, December 4, 2006 4:14 PM
  • Ok... that's not the solution, as I was still having that problem.  However, I did finally fix the problem.

    Here's how I wrote my code:

    I have these global variables in my class:

    Private blnUpdateText As Boolean = True
    Private custList As System.Windows.Forms.AutoCompleteStringCollection
    Private arLastNames As String()

    In my Me.Load event for my form, I set custList to the reference of the TextBox's AutoCompleteCustomSource:

    custList = Me.tbxLastName.AutoCompleteCustomSource

    And I am also handling the PreviewKeyDown and TextChanged events:

    Private Sub tbxLastName_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.PreviewKeyDownEventArgs) Handles tbxLastName.PreviewKeyDown
       
    'Catch up and down arrows, and don't change text box if these keys are pressed.
       
    If e.KeyCode = Keys.Up Or e.KeyCode = Keys.Down Then
           
    blnUpdateText = False
       
    Else
           
    blnUpdateText = True
       
    End If
    End Sub

    Private Sub tbxLastName_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbxLastName.TextChanged
       
    If blnUpdateText Then
           
    arLastNames = dsOrion.LookupLastName(Me.tbxLastName.Text) 'Returns a string array
           
    If arLastNames.Length > 0 Then
               
    custList.Clear()
                custList.AddRange(arLastNames)
           
    End If
       
    End If
    End Sub

    And I haven't had any problems since :-).  Hopefully this will be useful to some people.

    Tuesday, December 5, 2006 6:28 AM
  • Makoto,

    I have a very similar design for dynamic autocomplete, and have been plagued by this problem for a while now. I've spent several hours trying out different workarounds only for the memory access violation to turn up in a different context. Not clearing the autocomplete source and combining it with your workaround seems to have taken care of this issue for me. I have no idea how you arrived at it, but it's brilliant! Thank you very much.

     

    Tuesday, December 12, 2006 12:01 AM
  • Well... I can't say that that was a perfect solution.  If I type too fast for the autocomplete, it'd still give me problems. 

    What I ended up doing is loading every single last name from the database and populating the autocomplete source upon startup. Right now, there are only about 700 names that go in there, so it doesn't take very long to do over a local network, and the program still loads quickly. Now it doesn't have to read from the database every single time I type something, and it works perfect.  If I had a bigger database (like a list of millions of records) then this would be a different story, but that's what I ended up doing for now.

     

    Tuesday, December 12, 2006 3:35 AM
  • Hi,

    has anyone found a workign solution ? Thanks.

    Monday, February 26, 2007 10:30 AM
  • Hello,

    I've found something intresting. Please check out the www.skybound.ca website...They offer an extender autocomplete component that supports datasource! Just set the datasource for the control you need the autocomplete and you're ready to roll. The best thing about it (other than working with a datasource, that is!) they offer a FREE binary format, meaning you can use the .dll free of charge.

    Hope this helps,
    Al

    Tuesday, February 27, 2007 7:49 AM
  • I've been encountering the same problems with AccessViolationExceptions when dynamically setting my AutoCompleteCustomSource collection. I was able to reduce the frequency of crashes by no longer clearing the AutoCompleteCustomSource with the Clear() method and also using the AddRange() method for adding new strings to the collection instead of adding each string one at a time using Add().

    However, I still get AccessViolationExceptions thrown fairly regularly which makes the autocomplete functionality unusable in a production application.

    Has anyone found a solution to this issue? Do I need to update my AutoCompleteCustomSource in some other event handler or must I populate it entirely from the start instead of updating it dynamically as the text in my TextBox changes?

    Thanks for any assistance.
    Friday, March 2, 2007 4:28 PM
  • This is definately using COM to implement it under the covers.

    I checked using Reflector the StringSource object (which is private to WinForms, and used by the CustomSource internally)

    The object created seems to be the Microsoft AutoComplete object (00BB2763-6A77-11D0-A535-00C04FD7D062) which is in the System32\browseui.dll, which is marked as apartment threaded...

    Looking through the code (haven't tried any of the solutions), the best bet looks like anything that will "release" and recreate the autocomplete object. Most likely setting AutoCompleteSource to none, updating list, then resetting it again as ploxis suggested.

    I would also recommend the use of a timer to wait for sometime before searching for data (500ms - 1000ms) this permits typing more data before doing search...

    I know I'm going to have to implement this also soon too...
    Hope the suggestions work...

    Wednesday, March 21, 2007 3:26 AM
  • Due to the fact that this was not working perfectly for me, what I ended up doing is pre-populating the text box with all the records.  Since I currently only have about 700 records, this isn't too big of a deal and there haven't been any performance issues.  If I had millions of records however, this would not be an ideal solution. 

     

     

    The autocomplete text box seems to be a bit difficult to dynamically populate on the fly.  Hopefully someone can find a simple solution to this problem.
    Friday, March 30, 2007 7:35 PM
  • Hi All
    I also had the problem that sometimes an AccessViolationException was thrown when I set the AutoCompleteCustomSource multiple times in the TextChanged handler. I found a solution, but this solution has one drawback, when typing in a new character, the selection list is not displayed, only after the second character.

    Here is my Form, sorry I didn't know how to attach a zip file or if this is even possible? My Form contains only a TextBox and a BackgroundWorker control. I implemented my solution with a BackgroundWorker which is started in the TextChanged eventhandler. The PreviewKeyDown handler is used that we can navigate with the up and down keys to select an entry, otherwise it doesn't work properly. Feel free to copy the code and perhaps someone finds out why the selection list is displayed only after the second character? I didn't find the solution, i tried several possibilities:

    1. After setting the AutoCompleteCustomSource when the BackGroundWorker is finished, I call again the TextChanged handler etc. --> didn't help

    Thanks for your help...

    Best regards
    Peter

    Code Snippet


    public partial class TextBoxAutoComplete : Form
        {
            #region Constructor

            public TextBoxAutoComplete()
            {
                InitializeComponent();

                this.txtAutoComplete.AutoCompleteMode = AutoCompleteMode.Suggest;
                this.txtAutoComplete.AutoCompleteSource = AutoCompleteSource.CustomSource;
            }

            #endregion

            #region EventHandlers

            private bool doReloadData = true;
            private void txtAutoComplete_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
            {
                if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
                {
                    doReloadData = false;
                }
                else
                {
                    doReloadData = true;
                }
            }

            private string previousChar = null;
            private void txtAutoComplete_TextChanged(object sender, EventArgs e)
            {
                if (doReloadData)
                {
                    if (txtAutoComplete.Text.Length > 0)
                    {
                        if (previousChar == null)
                        {
                            RunBackroundWorker();
                        }
                        else if (!previousChar.Equals(txtAutoComplete.Text.Substring(0, 1)))
                        {
                            RunBackroundWorker();
                        }

                        previousChar = txtAutoComplete.Text.Substring(0, 1);
                    }
                }
            }

            AutoCompleteStringCollection collection = null;
            private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {
                OleDbConnection connection = null;
                OleDbCommand command = null;
                OleDbDataReader reader = null;
                string connString = "Provider=MSDAORA;DataSource=orsl;User ID=TB2007_LM_DEMO_102_MCK;Password=avs;";
                string sql = "select f_class_name from TB_Dictionary where UPPER(f_class_name) like UPPER('{0}%') order by f_class_name";
                sql = string.Format(sql, e.Argument.ToString());

                try
                {
                    //Create a connection to the Oracle database by using MSDAORA.
                    connection = new OleDbConnection();
                    connection.ConnectionString = connString;
                    connection.Open();

                    command = new OleDbCommand();
                    command.CommandText = sql;
                    command.Connection = connection;
                    reader = command.ExecuteReader();

                    // Always call Read before accessing data.
                    collection = new AutoCompleteStringCollection();
                    while (reader.Read())
                    {
                        collection.Add(reader.GetValue(0).ToString());
                    }

                    // Set the result.
                    e.Result = collection;
                }
                catch (OleDbException ex)
                {

                }
                finally
                {
                    connection.Close();
                    connection.Dispose();
                    command.Dispose();
                    reader.Close();
                }
            }

            private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                if (e.Cancelled)
                {
                    return;
                }

                this.txtAutoComplete.AutoCompleteCustomSource = e.Result as AutoCompleteStringCollection;
            }

            private void button1_Click(object sender, EventArgs e)
            {
                this.Close();
            }

            #endregion

            #region Private Methods

            private void RunBackroundWorker()
            {
                backgroundWorker1.RunWorkerAsync(txtAutoComplete.Text);
            }

            #endregion


    Thursday, May 31, 2007 10:07 AM
  • Has anyone come up with a definitive answer on the cause and fix for Autocomplete causing a System.AccessViolationException?  I've read through this entire thread and tried various suggestions, none which have entirely fixed the seemingly random crashing.
    Friday, June 1, 2007 4:44 AM
  • Did you also try the solution (the previous post) I propose with the BackgroundWorker? Did this solution also throw an System.AccessViolationException in your project? Because I couldn't recognize the crash with the BackgroundWorker...

    Best regards
    Peter
    Friday, June 1, 2007 6:19 AM
  • Nope, the background worker was the only thing I haven't tried -- only because it seems to make things even more complicated for a feature Microsoft touts as being simple to implement.

    How much did you test your solution with the background worker?  I've tested my current solution a lot; some times I can make it crash right away and yet other times (using basically the same data) I cannot.  Makes it challenging to really test what a 'good' solution is to this problem.
    Friday, June 1, 2007 7:29 AM
  • I can only say, that during implementation it never crashed, but this isn't a guarantee that it really works...So, for me it was the first solution which never throws the AccessViolationExcpetion.

    I'm also not happy with such a "complicated" solution, but it almost works, except that for the first time you enter a new character the selection list isn't displayed, only after the second character. But, I think this is a general problem, because I set the AutoCompleteCustomSource dynamically in the TextChanged eventhandler.

    In your solution, you also try to set the AutoCompleteCustomSource in the TextChanged handler?
    Friday, June 1, 2007 7:51 AM
  • No, why does the customsource need to keep being changed?  Below is basically all the code I'm using related to autocomplete...

     

     

    Code Snippet

    //InventoryAdd.Designer.cs
                this.txtModel.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
                this.txtModel.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource;
                this.txtModel.Location = new System.Drawing.Point( 129, 29 );
                this.txtModel.Margin = new System.Windows.Forms.Padding( 4, 5, 4, 5 );
                this.txtModel.MaxLength = 100;
                this.txtModel.Name = "txtModel";
                this.txtModel.Size = new System.Drawing.Size( 180, 26 );
                this.txtModel.TabIndex = 0;
                this.txtModel.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler( this.txtModel_PreviewKeyDown );
                this.txtModel.Leave += new System.EventHandler( this.txtModel_Leave );
                this.txtModel.Validating += new System.ComponentModel.CancelEventHandler( this.txtModel_Validating );
                this.txtModel.TextChanged += new System.EventHandler( this.txtModel_TextChanged );


    //InventoryAdd.cs
    namespace RTO.UI
    {
        public partial class InventoryAdd : MasterForm
        {       
            InventoryData inv = new InventoryData();
            Boolean bSuccess, bUpdateText;
            AutoCompleteStringCollection modelcollection;

            private void AddInventory_Load(object sender, EventArgs e)
            {
                modelcollection = new AutoCompleteStringCollection();
                txtModel.AutoCompleteCustomSource = modelcollection;
                bUpdateText = true;
               
                txtModel.Focus();
            }

            private void txtModel_TextChanged( object sender, EventArgs e )
            {
                if ( bUpdateText )
                {
                    //modelcollection.Clear();
                    modelcollection.AddRange( Singleton<Inventory>.Instance.GetModelNames( txtModel.Text ) );                   }
            }

            private void txtModel_PreviewKeyDown( object sender, PreviewKeyDownEventArgs e )
            {
                if ( e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Tab )
                    bUpdateText = false;
                else if ( e.KeyCode == Keys.Enter )
                    txtModel_Leave( sender, e );
                else
                    bUpdateText = true;
            }

            private void txtModel_Leave(object sender, EventArgs e)
            {
                if ( txtModel.Text.Length > 0 )
                {
      //fetch model details into form
                }
            }

    //Inventory.cs

            /// <summary>
            /// Returns a list of model names for autocompletion
            /// </summary>
            /// <param name="name">Partial name to look up</param>
            /// <returns></returns>
            public String[] GetModelNames( String name )
            {
                List<String> tlist = new List<String>( Singleton<Security>.Instance.DBConnection.GetModelNames( name.ToUpper() ) );
                Console.WriteLine( "tlist size: {0}", tlist.Count );
                return tlist.ToArray();
            }

     

     

    Friday, June 1, 2007 8:06 AM
  • Hi all,

     

    I tried nearly the code BSOD2600 does and got the Exception, now I tried even the BackgroundWorker-solution of Andres Peter and still get the exception.

     

    What makes me wonder ... it works now and then and suddenly you get an exception .

     

    If anyone had any more ideas, it would be great.

     

    We have some hierarchical data and want the AutoComplete to work like e.g. with the FileSystem or FileSystemDirectories source, so we tried changing the AutoCompleteCustomSource in the TextChange-event.

     

    Thanks for the discussion so far,

    McLohm

     

    Friday, June 1, 2007 1:30 PM
  • You can implement IEnumString and create your own autocomplete object.
    After you changed the data source of your autocomplete object, you need to clear the text in the control so the autocomplete object will discard the existing cache and reload.

               
    Code Snippet

    if(textBoxDemo->SelectionStart>0)
                {
                    autocompleteBindingSource1->Reset();
                    autocompleteBindingSource1->Bind();
                    String^ text=textBoxDemo->Text;
                    int selectionStart=textBoxDemo->SelectionStart;
                    int selectionLength=textBoxDemo->SelectionLength;
                    textBoxDemo->SelectionStart=0;
                    textBoxDemo->SelectionLength=0;
                    textBoxDemo->SelectAll();            
                    System::Windows::Forms:Tongue TiedendKeys:Tongue TiedendWait("{BACKSPACE}");
                    textBoxDemo->Text=text;
                    textBoxDemo->SelectionStart=selectionStart-1;
                    textBoxDemo->SelectionLength=selectionLength+1;
                    System::Windows::Forms:Tongue TiedendKeys:Tongue TiedendWait(textBoxDemo->SelectedText);
                }
    code sample +can be downloaded here
    Monday, August 6, 2007 3:16 AM
  • Update: IAutoCompleteDropDown seems to work
     
    Code Snippet

        public partial class FormAutoComplete : Form
        {
            object autoComplete ;
            CandidateList candidateList;
            int stringLength = 10;
            int randomStrings = 10000;
            Random rnd = new Random((int)System.DateTime.Now.Ticks);
            DynamicAutoCompleteCSharp.NativeMethods.IAutoComplete iAutoComplete;
            DynamicAutoCompleteCSharp.NativeMethods.IAutoComplete2 iAutoComplete2;
            DynamicAutoCompleteCSharp.NativeMethods.IAutoCompleteDropDown iAutoCompleteDropDown;
            public FormAutoComplete()
            {
                InitializeComponent();
                string[] data = new string[randomStrings];
                GenerateCandidates(data, null);
                candidateList= new CandidateList(data);
            }
            void GenerateCandidates(string[] data,string prefix)
       {
        bool addPrefix=!String.IsNullOrEmpty(prefix);
        StringBuilder text=new StringBuilder();
        for (int i = 0; i < randomStrings; i++)
        {
                        text.Remove(0, text.Length);
         if(addPrefix)
          text.Append(prefix);
                        text.Append(GenerateRandomText(rnd, 'a', 'z', stringLength));
                        data[i] = text.ToString();
         
        }
       }
            private static string GenerateRandomText(Random rnd,char minValue, char maxValue, int stringLength)
            {
               
                System.Text.StringBuilder randomText = new
                System.Text.StringBuilder(stringLength);

                //the range that we are allowed to go above the min value
                int randomRange = maxValue - minValue;

                double rndValue;

                for (int i = 0; i < stringLength; i++)
                {
                    rndValue = rnd.NextDouble();

                    randomText.Append((char)(minValue + rndValue * randomRange));
                }
                return randomText.ToString();
            }
            private void FormAutoComplete_Load(object sender, EventArgs e)
            {
                InitializeAutoComplete();
                InitializeEditControl();
            }

            private void InitializeEditControl()
            {
                AutoCompleteMode autoCompleteMode = textBox1.AutoCompleteMode;
                textBox1.AutoCompleteMode = AutoCompleteMode.None;
                iAutoComplete2.SetOptions((uint)autoCompleteMode);
                IEnumString iEnumString = candidateList;
                iAutoComplete2.Init(
                    new HandleRef(textBox1, textBox1.Handle),
                    iEnumString, String.Empty
                    , String.Empty);
            }

            private void InitializeAutoComplete()
            {
                Type typeAautoComplete = Type.GetTypeFromCLSID(new Guid("{00BB2763-6A77-11D0-A535-00C04FD7D062}"));
                autoComplete = Activator.CreateInstance(typeAautoComplete);
                iAutoComplete = (DynamicAutoCompleteCSharp.NativeMethods.IAutoComplete)autoComplete;
                iAutoComplete2 = (DynamicAutoCompleteCSharp.NativeMethods.IAutoComplete2)autoComplete;
                iAutoCompleteDropDown = (DynamicAutoCompleteCSharp.NativeMethods.IAutoCompleteDropDown)autoComplete;
            }

            private void textBox1_TextChanged(object sender, EventArgs e)
            {
                if (string.IsNullOrEmpty(textBox1.Text)) return;
                uint status = 0;
                StringBuilder text;
                try
                {
                    iAutoCompleteDropDown.GetDropDownStatus(out status, out text);
                }
                catch (Exception)
                {
                    return;
                }

                if (text == null) text = new StringBuilder(textBox1.Text);
                string[] data = new string[randomStrings];
                GenerateCandidates(data, text.ToString());
                candidateList.ReplaceCandidateList(data);
                iAutoCompleteDropDown.ResetEnumerator();
            }
        }


    project source code can be downloaded at http://cid-1be894deaf296e0a.skydrive.live.com/self.aspx/code/AutoComplete.zip
    Sunday, March 30, 2008 6:34 PM
  • I'm using VS2008 and everything works fine with autocomplete custom sources, just avoid let the user modify the text while you are updating the source and be sure to do not include null string in the in the source or the app will crash.
     
    Hope this help.
     
    Bye.
     
    Wednesday, April 30, 2008 4:12 PM
  •  Morpheus72 wrote:
    I'm using VS2008 and everything works fine with autocomplete custom sources, just avoid let the user modify the text while you are updating the source and be sure to do not include null string in the in the source or the app will crash.
     
    Hope this help.
     
    Bye.
     
    for me also same problem,any one  can help on that
    Monday, September 15, 2008 12:45 PM
  • All,
    I had the same problem but was able to find a solution that did not throw the memory error.
    here is my code.
    This code works both for Suggest and Append.

    declare this global variable in the form

    Private blnUpdateText As Boolean = True 


    then add the following code to your TextChange and KeyPress of your textbox.

        Private Sub txtSearch_TextChanged(ByVal sender As ObjectByVal e As System.EventArgs) Handles txtSearch.TextChanged  
            'only update the list if there is at least on character in the textbox and the user did not hit the backspace key
            If blnUpdateText = True And txtSearch.TextLength > 0 Then 
                'get a list of all the search text starting with the characters in the textbox  
                Using dstData As DataSet = YourDataSet(txtSearch.Text)  
                    'only update the list if there is data in the dataset  
                    If dstData.Tables(0).Rows.Count > 0 And txtSearch.Text <> "" Then 
                        For Each dRow As DataRow In dstData.Tables(0).Rows  
                            'add items to the list  
                            txtSearch.AutoCompleteCustomSource.Add(dRow("SearchResults"))  
                        Next 
                    End If 
                End Using  
                ' don't keep looping through the auto complete characters  
                blnUpdateText = False 
            End If 
        End Sub 
     
        Private Sub txtSearch_KeyPress(ByVal sender As ObjectByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtSearch.KeyPress  
            'keep the previous list if the user is hitting the backspace  
            If Asc(e.KeyChar) = Keys.Back Then 
                blnUpdateText = False 
            Else 
                blnUpdateText = True 
            End If 
        End Sub 
    Sunday, March 8, 2009 7:54 AM
  • j_l_brooks,

    i tried using your code but could not run my program. when I replaced "Mydataset" with the name of my dataset, intellisense told me that the dataset class did not have a default value assigned and therefors could not be used. Any ideas what i'm doing wrong?

    Wednesday, April 15, 2009 4:27 AM
  • Hi,

    I have been facing the same issue. I inspected and learned that if you have null as a value in suggestions it always crashed without a clue. Just get rid of all null strings from your autocomplete source and it works like magic!

    hope that helps,

    Digvijay
    Wednesday, December 9, 2009 5:31 PM
  • Hi,

    I have been facing the same issue. I inspected and learned that if you have null as a value in suggestions it always crashed without a clue. Just get rid of all null strings from your autocomplete source and it works like magic!

    hope that helps,

    Digvijay
    This is the solution! Test your strings list for null/empty values (use string.IsNullOrEmpty())
    Saturday, December 12, 2009 3:10 PM
  • Sorry, the example I had given was somewhat incorrect.
    The "YourDataSet" is a function that returns a dataset using some kind of datasource with a filter on the "txtSearch.Text"
    Here is full source code that will work using LINQ for example. Requires Winform with one textbox and 3.5 framework.

    Public Class Form1
    
        'This is a required variable that is used to test for the backspace key press
        Private blnUpdateText As Boolean = True
    
        'This is a global dataset that is not normally needed but is used for testing in this project
        Private dstNumbers As New DataSet()
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            'This section is not normally needed but is used for testing in this project
    
            Dim dtNumbers As New DataTable("Numbers")
            dstNumbers.Tables.Add(dtNumbers)
    
            'create a data column in the datatable / dataset
            dstNumbers.Tables("Numbers").Columns.Add("Number", Type.GetType("System.String"))
    
            'fill the datatable with test numbers
            For intCounter As Integer = 1 To 100
                Dim dr As DataRow = dtNumbers.NewRow()
                dr.Item(0) = intCounter.ToString
                dtNumbers.Rows.Add(dr)
            Next
    
            'NOTE: The textbox must have the following properties set
            txtSearch.AutoCompleteMode = AutoCompleteMode.SuggestAppend
            txtSearch.AutoCompleteSource = AutoCompleteSource.CustomSource
        End Sub
    
        Private Sub txtSearch_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtSearch.KeyPress
            'keep the previous list if the user is hitting the backspace   
            If Asc(e.KeyChar) = Keys.Back Then
                blnUpdateText = False
            Else
                blnUpdateText = True
            End If
        End Sub
    
        Private Sub txtSearch_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtSearch.TextChanged
            'only update the list if there is at least one character in the textbox and the user did not hit the backspace key 
            If blnUpdateText = True And txtSearch.TextLength > 0 Then
                'get a list of all the search text starting with the characters in the textbox
                ' using a function to fill the dataset with data that matches passed in characters for filters
                Using dstData As DataSet = FilterDataSet(txtSearch.Text)
                    'only update the list if there is data in the dataset   
                    If dstData.Tables("Numbers").Rows.Count > 0 And txtSearch.Text <> "" Then
                        For Each dRow As DataRow In dstData.Tables("Numbers").Rows
                            'add items to the list
                            txtSearch.AutoCompleteCustomSource.Add(dRow("Number"))
                        Next
                    End If
                End Using
                ' don't keep looping through the auto complete characters   
                blnUpdateText = False
            End If
    
        End Sub
    
        Private Function FilterDataSet(ByVal txtSearch As String) As DataSet
            'This is a function to filter data from datasource.
            'In this example I filter a global dataset but normally you would
            '   use a sql backend with a stored procedure using the SQL LIKE command to filter data
    	'  I would also use a TOP X command in the select to not populate some many records.
    Dim dstOut As New DataSet Dim dtOut As New DataTable("Numbers") Try 'using LINQ for sample Dim query = From Numbers _ In dstNumbers.Tables("Numbers") _ Where Numbers("Number").ToString.ToLower.StartsWith(txtSearch.ToLower) _ Select Numbers dtOut = query.CopyToDataTable() dtOut.TableName = "Numbers" Catch ex As Exception 'eat errors Finally 'send table even if blank dstOut.Tables.Add(dtOut) End Try Return dstOut End Function End Class
    • Edited by ImGonaRot Monday, January 4, 2010 9:57 PM full code
    Monday, January 4, 2010 9:55 PM
  • I ran into the same problem. 

    For me, it doesn't crash when I run the .Clear() or AddRange(*) operation.

    It's actually when I EXIT the function.

     

    This is what I noticed. The crash occurs when you press a key after updating a TextBox. The problem doesn't occur when you paste information.

    Try it. Do a Ctrl+V and it won't crash (but it won't paste either).

    What happens is, your cursor position is lost when you refresh the CustomSource.

    This is what happens

     

    1) CustomSource Change thread is called

    2) The entire textbox control is disposed (old pointers invalid)

    3) The entire textbox is rebuilt with new CustomSource

    4) The entire textbox control is redrawn (fresh new pointers)

     

    If you press a key before you the computer gets to step 4, the keys will be send to the old textbox pointer and it'll be lost. This means that the application is writing to an invalid part of memory and that's why you get memory errors. 

    I confirmed this by only triggering the CustomSource to refresh after a space registered in the Textbox's text.

    If I press 1,2,3, {SPACE} and stop, the Textbox will flash (it's being redrawn) and all my text is now selected (highlighted).

    After pressing 1,2,3, {SPACE} if IMMEDIATELY press another key, I'll get a memory access violation (no pointers). If you wait, about 600ms, the textbox will flash and the computer will allow keys to be inputed.

    What I do now is I call

    .Focus()

     and then

    .Select(textBox1.Text.Length, 0);

    to reset my cursor position. This will prevent the application from crashing with keyboard input.

     

    Also when the textbox is recreated, the text is actually changed to null and then back to the old value. This causes the Text_Changed Event to be looped. I added a bool check for this. Now the application won't crash so often.

     

    For references, this is my code:

    bool updating = false;
    private void PUcboLocation_TextChanged(object sender, EventArgs e)
    {
     if (updating == false && 
      string.IsNullOrEmpty(PUcboLocation.Text) == false &&
      PUcboLocation.Text.LastIndexOf(' ') == PUcboLocation.Text.Length - 1 &&
      PUcboLocation.Text.IndexOf(' ') == PUcboLocation.Text.LastIndexOf(' '))
     {
      updating = true;
      //create newStreets
      string[] newStreets = new string[streets.Length];
      for (int i = 0; i < streets.Length; i++)
      {
       string input = PUcboLocation.Text + streets[i];
       if (string.IsNullOrEmpty(input) == false)
        newStreets[i] = input;
      }
      PUcboLocation.AutoCompleteCustomSource.Clear();
      PUcboLocation.AutoCompleteCustomSource.AddRange(newStreets);
      PUcboLocation.Focus();
      PUcboLocation.Select(PUcboLocation.Text.Length, 0);
      updating = false;
     }
    }
    

     

    TLDR:

    When you refresh the Autocomplete List, the control is recreated with new pointers. Keyboard and mouse events (KeyPress, MouseOver, MouseLeave, MouseHover) attempt to reference the old control's pointers which are now invalid in memory causing a memory access violation to occur.

    Friday, May 21, 2010 9:20 PM
  • I'm using this

     

    This eliminates the crashing but I'm having issues with it hanging. Almost 100% production environment ready. Basically, if you every change the AutoCompleteList wait (lock the thread) until the new handle (pointer) is created

     

     

            if (PUcboLocation.AutoCompleteCustomSource.Count > 0)
            {
              IntPtr preClearHandle = PUcboLocation.Handle;
    
              PUcboLocation.AutoCompleteCustomSource.Clear();
              while (PUcboLocation.Handle == preClearHandle)
                Application.DoEvents();
            }
    
            IntPtr preAddHandle = PUcboLocation.Handle;
            PUcboLocation.AutoCompleteCustomSource.AddRange(streets);
            while (PUcboLocation.Handle == preAddHandle)
              Application.DoEvents();
    

     

    Friday, May 21, 2010 10:05 PM
  • The solution that worked for me is UGLY, but seems to be stable. I was using the ListItems as the autocomplete source, but I needed to dynamically add/remove list items from the combo box. I was doing so in the TextChanged event, and after modifying items in the combo box I needed to call "ddlControl.AutoCompleteSource = AutoCompleteSource.ListItems" and ddlControl.AutoCompleteMode = AutoCompleteMode.Suggest" to get the behavior I wanted. However, I would get the memory exception error randomly after exiting the TextChanged event handler.

    This was my solution:

    In the TextChanged handler, when manipulating the autocomplete properties, do so in a new thread as follows:

    Thread th = new Thread(new ThreadStart(delegate()
    {
      ddlControl.Invoke(new MethodInvoker(delegate()
      {
       ddlControl.AutoCompleteSource = AutoCompleteSource.ListItems;
       ddlControl.AutoCompleteMode = AutoCompleteMode.Suggest;
       ddlControl.Select(ddlControl.Text.Length, 0);
      }));
    }));
        
    th.SetApartmentState(ApartmentState.STA);
    th.Start();

    Invoke() is required to ensure that execution is marshalled to the thread that created the control. This avoids cross-threading exceptions.

    I don't like this solution because it is really ugly, but it's the only thing that has worked in my situation. I'm sticking with it for now until I find something better. I've already wasted enough time on it!


    Hazzard
    • Proposed as answer by Rufis_ Monday, August 3, 2020 8:52 PM
    Monday, June 28, 2010 8:33 PM
  • 3 years later, I find myself looking at this thread again due to a new .NET 4 WinForm project.   Same issues as before documented by many others in here with the Textbox autocomplete being very unstable code/implementation.

    This time around, I've opted to go with using an ElementHost which loads a WPF control. This custom WPF control basically is a System.Windows.Controls.Input.Toolkit.AutoCompleteBox, button, and a few radio buttons.  I'm using the autocomplete with a SQL database backend and so far with all the testing, it's been 100% reliable and functional.  Yes, there is an additional performance hit for loading winform and wpf assemblies... but that's what a loading screen is for ;)

    Friday, October 22, 2010 11:27 PM
  • Whats about this, guys?

    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.Runtime.InteropServices;

    namespace tWinTB
    {
        public partial class Form1 : Form
        {
            Timer wintimer;

            public Form1()
            {
                InitializeComponent();
            }

            /// <summary>
            /// Verwendete Ressourcen bereinigen.
            /// </summary>
            /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
            protected override void Dispose(bool disposing)
            {
                if (wintimer != null)
                {
                    wintimer.Stop();
                    wintimer.Dispose();
                    wintimer = null;
                }

                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            private void Form1_Load(object sender, EventArgs e)
            {
                textBox3.AutoCompleteSource = AutoCompleteSource.CustomSource;
                textBox3.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            }

            bool inLoad;
            private void textBox3_TextChanged(object sender, EventArgs e)
            {
                if (!inLoad)
                {
                    if (wintimer != null)
                    {
                        wintimer.Stop();
                        wintimer.Dispose();
                        wintimer = null;
                    }

                    // Start a timer to make Dropdown
                    wintimer = new Timer();
                    wintimer.Tick += new EventHandler(wintimer_Tick);
                    wintimer.Interval = 1000;  // Only make DropDown if user waits for a while
                    wintimer.Start();
                }
            }

            string autoDone;
            void  wintimer_Tick(object sender, EventArgs e)
            {
                // Only Autocomplete if Textbox is waiting, Cursor is at the end of the text
                if (textBox3.Focused && textBox3.SelectionLength == 0 && textBox3.SelectionStart == textBox3.Text.Length)
                {
                    if (wintimer != null)
                    {
                        wintimer.Stop();
                        wintimer.Dispose();
                        wintimer = null;

                        // only make AutoComplete for the beginning of the Text and remeber last
                        string autokey = textBox3.Text.Length > 0 ? textBox3.Text.Substring(0, 1) : "";
                        if (autoDone != autokey)
                        {
                            // New beginning, start autocomplete
                            autoDone = autokey;
                            inLoad = true;

                            textBox3.AutoCompleteCustomSource.Clear();

                            // Access the Database here:
                            if (autokey == "a")
                            {
                                textBox3.AutoCompleteCustomSource.Add("abc");
                                textBox3.AutoCompleteCustomSource.Add("abd");
                                textBox3.AutoCompleteCustomSource.Add("abef");
                                textBox3.AutoCompleteCustomSource.Add("abeg");
                            }
                            if (autokey == "x")
                            {
                                textBox3.AutoCompleteCustomSource.Add("xxxxxxxxx");
                                textBox3.AutoCompleteCustomSource.Add("xxx2");
                                textBox3.AutoCompleteCustomSource.Add("xxx3");
                                textBox3.AutoCompleteCustomSource.Add("xyyyy");
                            }

                            // Open DropDowns directly
                            if ( textBox3.Text.Length<textBox3.MaxLength )
                                SendKeys.Send(" {BKSP}");

                            inLoad = false;
                        }
                    }
                }
            }
        }
    }

    Friday, January 25, 2013 6:45 AM