Thread: How can this WinForm functionality be implemented?

  1. #1
    Registered User
    Join Date
    Sep 2011
    Posts
    71

    How can this WinForm functionality be implemented?

    Without going into too many details, picture a Windows form that has only one button and one muti-line textbox.

    When the button is clicked, an event handler procedure retrieves text and, when it's done retrieving it, displays it on the textbox, by assigning the expression that represents the retrieved text to the textbox's Text property.

    Assume that no matter how the code is optimized, it's going to take a while before all the text is retrieved. The problem is that while the text is being retrieved, the Windows form remains "frozen", and interaction with the form is temporarily blocked.

    I don't want that to happen. I want the form to remain usable while the text is being retrieved, and then, when the text is retrieved, I want it to appear on the textbox, even if it takes a while before that happens.

    I tried to solve this problem by spawning a thread inside the event handler procedure and letting that thread retrieve the text, but .NET throws an exception when the spawned thread tries to set the textbox's Text property or interact with any of the form's controls.

    Forcing the main thread to wait for the spawned thread to finish doing its work, and then letting the main thread set the textbox's Text property with the text retrieved by the spawned thread doesn't solve the problem, because the form remains frozen while the main thread waits for the spawned thread.

    I also tried including within the event handler procedure a call to the form's invoke method and passing as an argument a reference to a delegate procedure that retrieves the text, but the form remained frozen until the text was retrieved.

    Can someone tell me how to solve this problem?
    Last edited by y99q; 02-27-2012 at 10:18 PM.

  2. #2
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Quote Originally Posted by y99q View Post
    I tried to solve this problem by spawning a thread inside the event handler procedure and letting that thread retrieve the text, but .NET throws an exception when the spawned thread tries to set the textbox's Text property or interact with any of the form's controls.
    Right approach, but missing one piece; you need to have the spawned thread invoke the text change on the main thread.

    Something like this will work:

    Code:
            private delegate void SetOutputTextHandler(string s);
            private void SetOutputText(string s)
            {
                if (InvokeRequired)
                {
                    BeginInvoke(new SetOutputTextHandler(SetOutputText), new object[] { s });
                }
                else
                {
                    lblOutput.Text = s;
                }
            }
    This uses the form's InvokeRequired to determine if it needs to invoke the method in the main form thread, or not. This method can be safely called in either the main or worker thread, and the actual control access will occur only in the main thread.

    You can also check out the BackgroundWorker class; you can put the text update in the RunWorkerCompleted event which occurs when the worker is done. The advantage of this class is that you can easily do things like a progress bar without needing to worry about Invoke; that happens behind the scenes.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  3. #3
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    the following implementation is based on my understanding of the change that you suggested. how come the form still remains frozen until the text is displayed on the textbox control? (by the way, the form remains "semi-frozen" when I use a string instead of a stringbuilder. that's an improvement, but still not what I'm looking for.)

    Code:
            private void button1_Click(object sender, EventArgs e)
            {
                Thread t = new Thread(new ThreadStart(DelegateProcedure));
                t.Start();
            }
    
            private void DelegateProcedure()
            {
               Random random = new Random();
               StringBuilder sb = new StringBuilder();
    
               for (int x = 0; x <= 50000; x++)
                   sb.Append(random.Next(0, 100));
    
                if (this.InvokeRequired) this.BeginInvoke(new Action<string>(SetOutputText),
                    new object[]{sb.ToString()});
            }
    
            private void SetOutputText(string s)
            {
                textBox1.Text = s;
            }
    Last edited by y99q; 02-28-2012 at 01:19 AM.

  4. #4
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Your understanding is correct. To see what's happening, I added some timing code:

    Code:
            Stopwatch textboxTiming, threadTiming;
            private void button1_Click(object sender, EventArgs e)
            {
                Thread t = new Thread(new ThreadStart(DelegateProcedure));
                t.Start();
            }
    
            private void DelegateProcedure()
            {
                Random random = new Random();
                StringBuilder sb = new StringBuilder();
    
                threadTiming = new Stopwatch();
                threadTiming.Start();
                for (int x = 0; x <= 50000; x++)
                    sb.Append(random.Next(0, 100));
                threadTiming.Stop();
    
                if (this.InvokeRequired) this.BeginInvoke(new Action<string>(SetOutputText),
                    new object[] { sb.ToString() });
            }
    
            private void SetOutputText(string s)
            {
                Stopwatch textboxTiming = new Stopwatch();
                textboxTiming.Start();
                textBox1.Text = s;
                textboxTiming.Stop();
    
                MessageBox.Show(string.Format("Spent {0} ms in thread, {1} ms in SetOutputText()", threadTiming.ElapsedMilliseconds, textboxTiming.ElapsedMilliseconds));
            }
    On my computer, the thread took 16 ms to execute, and setting the textbox text took over 2 seconds. That's why the main thread stalls - the longest portion of the execution is happening in that thread.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  5. #5
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    It seems that the multiline textbox is the issue. It has to break the text into lines. If I use a single-line textbox it's almost instantaneous.

    It works better if you split the text into lines yourself in the other thread. This takes about 400 ms in the main thread (still nontrivial but a lot better):

    Code:
            Stopwatch textboxTiming, threadTiming;
            private void button1_Click(object sender, EventArgs e)
            {
                Thread t = new Thread(new ThreadStart(DelegateProcedure));
                t.Start();
            }
    
            private void DelegateProcedure()
            {
                Random random = new Random();
                string[] str = new string[50001];
    
                threadTiming = new Stopwatch();
                threadTiming.Start();
                for (int x = 0; x <= 50000; x++)
                    str[x]=random.Next(0, 100).ToString();
    
                threadTiming.Stop();
    
                if (this.InvokeRequired) this.BeginInvoke(new Action<string[]>(SetOutputText),
                    new object[] { str });
            }
    
            private void SetOutputText(string[] s)
            {
                Stopwatch textboxTiming = new Stopwatch();
                textboxTiming.Start();
                textBox1.Lines = s;
                textboxTiming.Stop();
    
                MessageBox.Show(string.Format("Spent {0} ms in thread, {1} ms in SetOutputText()", threadTiming.ElapsedMilliseconds, textboxTiming.ElapsedMilliseconds));
            }
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  6. #6
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    so the culprit is the textbox's WordWrap functionality. who would have thought it?

    for now I'll do something like this:

    Code:
                for (int x = 0; x <= 500000; x++)
                {
                    if (x > 0 && x % 35 == 0)
                       sb.AppendLine();
     
                    sb.Append(random.Next(0, 10));
                }

  7. #7
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    When the button is clicked, an event handler procedure retrieves text and, when it's done retrieving it, displays it on the textbox, by assigning the expression that represents the retrieved text to the textbox's Text property.
    This can be alleviated by data binding the control. This way your code never touches the text property of the control and thus no need for the thread. Bind the control to the data source and alter the data source. You can do this both in Windows Forms and WPF applications although WPF is light years ahead of Forms when it comes to data binding.

  8. #8
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    I am not very familiar with the concept of data binding. can you illustrate with some sample code how you would go about implementing this functionality?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. winform in c++ (or maybe c#) or WinAPI
    By gilit2 in forum Windows Programming
    Replies: 6
    Last Post: 02-06-2011, 06:06 PM
  2. Stopping Winform Process
    By darren78 in forum C# Programming
    Replies: 2
    Last Post: 10-16-2010, 10:55 AM
  3. WaitForExit(); Works, but C# WinForm disappears.
    By Dragoon_42 in forum C# Programming
    Replies: 7
    Last Post: 01-19-2010, 03:01 PM
  4. Replies: 7
    Last Post: 09-24-2006, 10:13 AM
  5. Write to Console from a WinForm
    By ginoitalo in forum C# Programming
    Replies: 2
    Last Post: 10-19-2003, 06:20 PM