0

I have a textbox and a listbox in my app. Also i have a text file with many players. What I want is whenever the user enters some text, look in the players file and add the matching players to the matching list which is the data source for the listbox. The problem is that it seems to be very slow and UI freezes a short time but it's quite annoying.

This is the code i have:

private void tb_playername_TextChanged(object sender, EventArgs e)
{

    //This method is used to show user the options he can choose with the text he has entered
    List<string> matching_players = new List<string>();

    foreach (var item in all_players)
    {
        string player = item.f_name + " " + item.l_name;

        if ((player.IndexOf(tb_playername.Text, StringComparison.OrdinalIgnoreCase) >= 0))
        {
            matching_players.Add("(" + item.rating + ") " + item.f_name + " " + item.l_name);
        }
    }

    if (tb_playername.Text.Length >= 4)
    {
        matching_players.Sort();
        matching_players.Reverse()                
        listbox_matchingplayers.DataSource = matching_players;
    }
}
Aldridge1991
  • 1,326
  • 6
  • 24
  • 51

2 Answers2

3

The problem is that you are doing a relatively time consuming task in the event handler. Event handlers operate on the same thread which takes care of rendering your application and handle any other visual aspects of it, so if this thread is busy, it will not be in a position to react to user input immediately, hence freezing.

The standard approach to this problem is to offload the time consuming tasks to a Background Worker. The background worker will operate in a new thread thus allowing the main thread to continue handling UI events. This example should hopefully put you on the right track when it comes to using a background worker.

EDIT: As per your question, what you could do would be to start searching only when a particular amount of characters is entered, for instance 3, this would reduce the amount of time the background worker runs. If the user keeps on typing, you could stop the current background worker if running and launch a new one.

The background worker will fire an event when finished. You could use the RunWorkerCompletedEventArgs.Result to then extract the returned list act upon it.

npinti
  • 51,780
  • 5
  • 72
  • 96
  • What about if user enters more text before BW is finished? – Aldridge1991 Jan 14 '15 at 09:05
  • He can't do this *as is* in a background thread as these are UI elements he's using. – Yuval Itzchakov Jan 14 '15 at 09:08
  • I generally prefer using async, await tasks as I find them simpler to write: http://msdn.microsoft.com/en-us/library/hh191443.aspx , http://msdn.microsoft.com/en-us/library/hh156513.aspx But when it is not more complex than the original question, this answer is more than sufficient. – St0ffer Jan 14 '15 at 09:09
  • @YuvalItzchakov Just return the List `matching_players` from the method after adding the items in a background thread and do the binding on the UI thread afterwards. – St0ffer Jan 14 '15 at 09:12
  • @Aldridge1991: Please take a look at the update answer. I hope this helps. – npinti Jan 14 '15 at 09:15
  • @St0ffer He can't touch the original list, he'll have to make a copy – Yuval Itzchakov Jan 14 '15 at 09:19
  • @YuvalItzchakov Of course not as the code looks now, hence why I suggested he makes a method that adds the items in a background thread. Something like `List matching_players = new List()` ... `await Task.Run(() => matching_players = GetMatchingPlayers());` – St0ffer Jan 14 '15 at 09:28
  • But I guess that is a copy as well, so you are right on that. – St0ffer Jan 14 '15 at 09:32
1
private async void tb_playername_TextChanged(object sender, EventArgs e)
{
  var text = (sender as TextBox).Text;

  // Check length of the text
  if (string.IsNullOrEmpty(text) || text.Length <= 3)
    return;

  // Check timer to not process if user still typing, by measuring the key stoke time
  ...

  // Filtering
  List<string> matching_players = await PlayerFilter(text);

  // Minimize listbox layout time
  listbox_matchingplayers.SuspendLayout();
  listbox_matchingplayers.DataSource = matching_players;
  listbox_matchingplayers.ResumeLayout();
}

//Time consuming method
private async Task<List<string>> PlayerFilter(string text)
{
  //This method is used to show user the options he can choose with the text he has entered

  return matching_players;
}

For details of the user typing check wait for user to finish typing in a Text Box

Community
  • 1
  • 1
Eric
  • 5,675
  • 16
  • 24