-1

I am working on a project in C# and I have to make console application and also windows forms application. I have completed the console application, but now I have a problem with "converting" it into windows forms. The application have to count euler number from infite series. My problem is that when I start it, it freezes and won't give me any output in the textbox. The output I want is of course numeric. Here is the code itself. I hope that it wouldn't be a problem to understand it without the gui.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

    namespace Eulerovo_cislo_windows_forms
   {
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {

        int konst = Int32.Parse(textBox1.Text);
        double euler = 1;

        int variable = 0; 
        int index = 0; 
        int factorial = 0; 
        double absolute_error; 
        variable = konst;

        richTextBox1.Text = ("---ongoing calculations---");
        while (true)
        {

            try
            {

          if (konst < 1 || konst > 30) throw new ArgumentOutOfRangeException("you have not entered a value between 1 and 30");
                while (variable > 0)
                {

                    if (variable == 1) 
                        factorial = 1;
                    else
                    {
                        index = variable - 1;

                        faktorial = variable; 



                        while (index > 0) 

                        {
                            factorial *= index; 
                            index--; 
                        } 
                    }
                    promenna--; 

                    euler += Convert.ToDouble(1) / factorial;  
                    richTextBox1.Multiline = true;
                    richTextBox1.Text = euler.ToString();



                }
            }
            catch (ArgumentOutOfRangeException ae) // exceptions
            {
                MessageBox.Show(ae.Message);
                break;
            }
            catch (FormatException fe)
            {

                MessageBox.Show(fe.Message);
                break;
            }

            finally
            {



                absolute_error = EulerNumberError.error(euler);

                textBox3.Text = absolute_error.ToString();
                textBox2.Text = euler.ToString();



            }
        }
    }

    private void button2_Click(object sender, EventArgs e)
    {
        Application.Exit();
    }


}
class EulerNumberError // absolute error 
        {
        public static double error(double a)
        {
            return Math.E - a;
        }

        }
}
  • 1
    You have the classic "I blocked my GUI thread" bug. Please research `BackgroundWorker` class, as the starting point for understanding how to execute your algorithm asynchronously so that the GUI thread can continue to run and provide feedback even as your algorithm does its work. – Peter Duniho Jan 07 '15 at 17:59
  • When exactly does it freeze? On load? After a certain action? – ryanyuyu Jan 07 '15 at 17:59
  • A while true with break only in case of exceptions. No wonder that your interface is frozen. – Steve Jan 07 '15 at 18:01
  • possible duplicate of [Winforms : avoid freeze application](http://stackoverflow.com/questions/1737088/winforms-avoid-freeze-application) – Peter Duniho Jan 07 '15 at 18:05
  • 1
    The first thing you need to do is create a separate class to handle the actual math problem, entirely devoid of your user interface, and move your business logic over there, so that you can separate out managing your UI from solving a math problem. – Servy Jan 07 '15 at 18:05
  • This question has been answered in different forms many times. Other possible duplicates include https://stackoverflow.com/questions/6789105/winforms-message-loop-not-responsive/6790646#6790646 and https://stackoverflow.com/a/1862633/3538012 – Peter Duniho Jan 07 '15 at 18:11

3 Answers3

0

You need to call it in a separate thread and update textbox in a UI thread. Your UI is frozen because you are taking up the Main thread and never giving a chance for your UI to update the display. Here is what you can try using BackgroundWorker.

private void button1_Click(object sender, EventArgs e)
{

    int konst = Int32.Parse(textBox1.Text);
    double euler = 1;

    int variable = 0; 
    int index = 0; 
    int factorial = 0; 
    double absolute_error; 
    variable = konst;

    richTextBox1.Text = ("---ongoing calculations---");

    richTextBox1.Multiline = true;

    BackgroundWorker worker = new BackgroundWorker();
    worker.ProgressChanged += (s, e) =>
    {
      string s = e.UserState as string;
      richTextBox1.Text = s;

    };
   worker.RunWorkerCompleted += delegate
   {
      textBox3.Text = absolute_error.ToString();

      textBox2.Text = euler.ToString();   
   };

    worker.DoWork += delegate
    {
    while (true)
    {

        try
        {

      if (konst < 1 || konst > 30) throw new ArgumentOutOfRangeException("you have not entered a value between 1 and 30");
            while (variable > 0)
            {

                if (variable == 1) 
                    factorial = 1;
                else
                {
                    index = variable - 1;

                    faktorial = variable; 



                    while (index > 0) 

                    {
                        factorial *= index; 
                        index--; 
                    } 
                }
                promenna--; 

                euler += Convert.ToDouble(1) / factorial;  

               worker.ReportProgress(0,  euler.ToString());

            }
        }
        catch (ArgumentOutOfRangeException ae) // exceptions
        {
            MessageBox.Show(ae.Message);
            break;
        }
        catch (FormatException fe)
        {

            MessageBox.Show(fe.Message);
            break;
        }

        finally
        {
            absolute_error = EulerNumberError.error(euler);
        }
    }
   }

   worker.RunWorkerAsync();
}

private void button2_Click(object sender, EventArgs e)
{
    Application.Exit();
}

}

fahadash
  • 3,133
  • 1
  • 30
  • 59
  • 1
    The whole *point* of using a `BackgroundWorker` is that it provides tools for updating the UI thread with the results of an operation run in another thread. Why are you using it and not using any of the functionality that it actually adds? – Servy Jan 07 '15 at 18:04
  • @Servy: I suspect because it was easier for him to write the answer that way. Actually wrapping progress updates correctly and handling the necessary events takes longer and requires more effort on the answerers part. ;) But hey, at least he got the calls to `Invoke()` in there. – Peter Duniho Jan 07 '15 at 18:10
  • @Servy Are you talking about the Progress Reporting stuff in BackgroundWorker ? – fahadash Jan 07 '15 at 18:10
  • @PeterDuniho If he doesn't want to use the BGW features at all then can can just not use it. That's fine. But if you're going to go out of your way to use a tool that provides enhanced support for a particular functionality, it makes no sense to then ignore all of it. That said, while it's not nearly as nice as what the TPL can do in C# 5.0, it *does* add value to use a BGW in that the UI and non-UI operations are separated, rather than intermingling the UI and non-UI code into a bunch of spaghetti code. – Servy Jan 07 '15 at 18:12
  • @fahadash Both reporting progress and the completion of the worker, yes. That's the entire reason to use it instead of a `Thread` in the first place. – Servy Jan 07 '15 at 18:12
  • @Servy: I'm not saying it makes sense. I'm just saying that in the context of SO, I think I understand why the answer didn't include that nicety. "Early bird gets the worm", and all that. The SO model gives a strong incentive to post _an_ answer as quickly as possible, and many people do. :) On the bright side, answers can be edited, and perhaps this one will be updated so that the `ProgressChanged` and `RunWorkerCompleted` events are taken advantage of (so that the calls to `Invoke()` aren't even needed). – Peter Duniho Jan 07 '15 at 18:21
  • Alright folks, the code is updated to use the advanced features of BGW. No fighting! – fahadash Jan 07 '15 at 18:39
  • So close. But while you added a handler for `RunWorkerCompleted`, you didn't actually take advantage of it. You are still using `Control.Invoke()` for the UI update in the `finally` clause. – Peter Duniho Jan 07 '15 at 18:42
  • @PeterDuniho Alright. Fixed. You guys are too picky :P – fahadash Jan 07 '15 at 18:44
0

So, you have an answer that shows how to do this using BackgroundWorker. But, as Servy has suggested and with which I agree, in the current version of .NET it would be better to implement this using the more modern idiom of async/await. Of course, when doing so it would be of important to actually take advantage of that idiom.

Here is a version of the Click event handler that does this:

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;

    int konst = Int32.Parse(textBox1.Text);
    double euler = 1;

    int variable = 0;
    int index = 0;
    int factorial = 0;
    double absolute_error;
    variable = konst;

    richTextBox1.Multiline = true;
    richTextBox1.ReadOnly = true;
    richTextBox1.Text = ("---ongoing calculations---");

    while (true)
    {
        try
        {
            if (konst < 1 || konst > 30)
            {
                throw new ArgumentOutOfRangeException("you have not entered a value between 1 and 30");
            }

            while (variable > 0)
            {
                await Task.Run(() =>
                {
                    int iterationMax = 100;

                    while (variable > 0 && iterationMax-- > 0)
                    {
                        if (variable == 1)
                        {
                            factorial = 1;
                        }
                        else
                        {
                            index = variable - 1;

                            factorial = variable;

                            while (index > 0)
                            {
                                factorial *= index;
                                index--;
                            }
                        }
                        //promenna--;

                        euler += 1d / factorial;
                    }
                });

                richTextBox1.Text = euler.ToString();
            }
        }
        catch (ArgumentOutOfRangeException ae) // exceptions
        {
            MessageBox.Show(ae.Message);
            break;
        }
        catch (FormatException fe)
        {

            MessageBox.Show(fe.Message);
            break;
        }
        finally
        {
            absolute_error = EulerNumberError.error(euler);

            textBox3.Text = absolute_error.ToString();
            textBox2.Text = euler.ToString();
        }
    }

    button1.Enabled = true;
}

Some notes:

  • I had to comment out the line decrementing "promenna", and I had to change the statement faktorial = variable; to factorial = variable;. I didn't bother to look too closely at the math here, so I don't really know if those are appropriate changes or not. Since the code posted never modifies the variable value, it's impossible for the algorithm to actually complete, so I have no way to know if those changes were even close to being correct.
  • The time it takes for each iteration of the outer loop is really too small for a UI update on each iteration. If one were to try to update on every iteration, the window's message queue would get filled up and cause the program to temporarily stop responding. So I added an intermediate counter for the work to ensure it spends enough time on each outer iteration that the UI thread can keep up. On my computer, a count of 100 was sufficient to mitigate that issue, but I was able to use a value as large as 10,000,000 while still seeing what I thought was a reasonable refresh speed on the UI. Higher values (especially above 10,000) work much better.
  • I added logic to disable the button while the work is going, to ensure against a user who tries to get more than one operation going at once.
  • There is code in this method to initialize properties of the RichTextBox object that really should just be set in the VS Designer.

Finally, I agree whole-heartedly with Servey's point that it would be better to abstract the math operation so that it is not dependent on the UI at all. I.e. it can provide some mechanism, either async or event-based, to allow for UI updates without the math code itself knowing anything about the UI. But the above should work and illustrates a correct way to use async/await in simple scenarios.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
0

Catch normal Exception in try block in button1_Click. It seems you have some exception but cannot see it because only ArgumentOutOfRangeException and FormatException are processed. And there is infinite loop around try-catch...

i486
  • 6,491
  • 4
  • 24
  • 41