1

This is my first class:

namespace WindowsFormsApplication2 
{

    public partial class Form1 : Form    
    {
        public Form1()
        {
            InitializeComponent();
            /*_enemy = new Class1(this);
            int y = Class1.MyMethod(0);
            textBox1.Text = Convert.ToString (y);*/
        }
        private Class1 _enemy;

        private void button1_Click(object sender, EventArgs e)
        {
            _enemy = new Class1(this);
            int y = Class1.MyMethod();
            textBox1.Text = Convert.ToString(y);
        }
    }
}

and this is my second class:

namespace WindowsFormsApplication2
{

    public class Class1    
    {    
        public Class1( Form1 form )
        {
            _form1 = form;
        }
        public static int MyMethod()
        {
            int i = 0;
            for (int j = 1; j <= 20; j++)
            {
                i = j;
                //Thread.Sleep(100);
            }
            return i;
        }
    }

    // DON'T initialize this with new Form1();
    private Form1 _form1;
}

The program is running correctly and I am getting only 20 as output in the TextBox. What I want is the output each time the loop runs.

Like 1,2,3,.........20 and stop.

This is the design of the form

Like a counter maybe. I also tried using Timer but couldn't do that.

EDIT:

@Mong Zhu I have cross checked the code, still getting the exception.

For your reference here are the complete codes:

Form1.cpp

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        Class1 MyCounterClass;
        private void Form1_Load(object sender, EventArgs e)
        {
            MyCounterClass = new Class1();
            // register the event. The method on the right hand side 
            // will be called when the event is fired
            MyCounterClass.CountEvent += MyCounterClass_CountEvent;
        }

        private void MyCounterClass_CountEvent(int c)
        {
            if (textBox1.InvokeRequired)
            {
                textBox1.BeginInvoke(new Action(() => textBox1.Text = c.ToString()));
            }
            else
            {
                textBox1.Text = c.ToString();
            }
        }

        public Form1()
        {
            InitializeComponent();
        }
        private Class1 _enemy;

        private void button1_Click(object sender, EventArgs e)
        {
            MyCounterClass.MyCountMethod(300, 0, 10);
        }

    }
}

and class1.cpp

namespace WindowsFormsApplication2
{
    public class Class1
    {
        public delegate void Counter(int c); // this delegate allows you to transmit an integer


public event Counter CountEvent;

public Class1()
    {

    }
         public void MyCountMethod(int interval_msec, int start, int end)
         {
             System.Threading.Thread t = new System.Threading.Thread(() =>
             {
                 for (int i = start; i <= end; i++)
                 {
                     // Check whether some other class has registered to the event
                     if (CountEvent != null)
                     {
                         // fire the event to transmit the counting data
                         CountEvent(i);
                         System.Threading.Thread.Sleep(interval_msec);
                     }
                 }
             });
             // start the thread
             t.Start();
         }

    // DON'T initialize this with new Form1();
        private Form1 _form1;
    }
}
Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
  • Are you looking for [IProgress](https://blogs.msdn.microsoft.com/dotnet/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis/)? – default Feb 20 '17 at 13:33
  • I do not know what it is? Can you elaborate? – mehta ankit Feb 21 '17 at 04:43
  • It is basically a contract that says that the producer (your form) want to know what the consumer (class1) is doing. It's also described [here](http://simplygenius.net/Article/AncillaryAsyncProgress) and [here](http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html). It's not exactly what your question title says, but reading your question seems to indicate it. – default Feb 21 '17 at 10:58
  • @Default can you help me out here. (With codes). What I need is a logic in class1 which will create a loop for counter. And I need to call that class1 inside form1 on a button click inside a textbox. but it might not necessarily be a text box it might be a progress bar or anything else. Basically I do not want to transfer any GUI from class1. – mehta ankit Feb 21 '17 at 11:06
  • Did you check the links? What I am suggesting is explained _in detail_ in both of those links. If that doesn't answer your question then no, I cannot help you with code. If that is what you are looking for then sure, I can add an answer with that. But please read the links first! – default Feb 21 '17 at 11:11
  • Yes @Default that is what I need. Can you help me with how it will be implemented – mehta ankit Feb 21 '17 at 11:17

3 Answers3

3

If you are looking to report progress from some object back to your form you can use the IProgress<T> interface instead. It is well explained here and here but to translate it to your given code it would look something like this:

public partial class Form1 : Form
{
    private async void button1_Click(object sender, EventArgs e)
    {
        Progress<int> reporter = new Progress<int>(number =>
        {
            textBox1.Text = number.ToString();
        });
        await Task.Run(() => MyClass1.MyMethod(reporter));
    }
}

public class Class1
{
    public static int MyMethod(IProgress<int> reporter)
    {
        for (int i = 1; i <= 20; ++i)
        {
            reporter.Report(i);
            //Thread.Sleep(100);
        }
        return i;
    }
}

Note that

  • Class1 does not need any knowledge of Form1.
  • Since Class1.MyMethod is static you do not require an instance of it. If you want to modify fields/properties in Class1 you need an instance. It's really up to you if this is correct or not.
  • IProgress<T> requires .NET Framework 4.5
default
  • 11,485
  • 9
  • 66
  • 102
2

The problem is that you only pass the last value to the GUI. What you could do is to pass the textbox which you want to use for display into your counting method MyMethod. There you could assign the value. One last thing that you need to do is to tell the application to update it's events with Application.DoEvents();

So your method would look like this:

public static int MyMethod(TextBox t)
{
    int i = 0;
    for (int j = 1; j <= 20; j++)
    {
        i = j;
        t.Text = j.ToString();
        Application.DoEvents();
        Thread.Sleep(200);
    }
    return i;
}

don't forget to include:

using System.Threading.Tasks;
using System.Windows.Forms;

in you Class1.cs

The call in Form1 would look like this:

private void button1_Click(object sender, EventArgs e)
{
    _enemy = new Class1(this);
    int y = Class1.MyMethod(textBox1);

}

Disclaimer: Application.DoEvents() should be avoided as pointed out by @Default. Therefore another approach and probably a preferable one would be to use a timer. It has a Tick event which could work like your for-loop. This one is System.Windows.Forms.Timer. You can use it in the Form1 class:

public partial class Form1 : Form
{
    Timer t = new Timer();

    public Form1()
    {
        InitializeComponent();
        t.Interval = 200;    // set the interval
        t.Tick += T_Tick;    // register to the event
    }

    int i = 0;  // this is your counting variable
    private void T_Tick(object sender, EventArgs e)
    {
        if (i<=20) // this takes care of the end
        {
            this.textBox1.Text = i.ToString();
            i++; // count up
        }
        else
        {
            t.Stop(); // stop the timer if finished
            i = 0;    // for the next time if you want to restart the timer
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        t.Start();  // now just start your timer
    }
}

EDIT

Ok lets make things a little more complicated but thorough. You asked:

i.e. call a method somewhere else and print somewhere else. By somewhere else I mean another class

If you want to print it somewhere else it will be somewhere else ;) What I mean is that the responsibility of the graphical user interface is to display things. So it should remain displaying things. The responsibility of your method is to count up, so it should remain counting things up. To combine these two responsibilities in C# the concept of events is a powerfull one. It allows you to signal events and transmit data.

The first thing you need is an event to signal the counting in Class1: It has 2 Parts. A delegate which defines the structure of the method that will be called when the event is fired, and the event of the type of the delegate that can be registered in another class. In your case the Form1.

public class Class1
{        
    public delegate void Counter(int c); // this delegate allows you to transmit an integer

    public event Counter CountEvent;

    public Class1()
    {
    }

I removed the instance of Form1 _form from Class1. Because you don't need it for the task. Also this makes your Class1 independent of the implementation of the GUI. (If you decide tomorrow to change the name of the TextBox or choose a Label to display the counter, there will be no changes to make in the Class1, only in Form1!) Now you can register/subscribe to the event in the Form1 and create the eventhandler which will be called when the event is fired:

Form1

Class1 MyCounterClass;

private void Form1_Load(object sender, EventArgs e)
{
    MyCounterClass = new Class1();
    // register the event. The method on the right hand side 
    // will be called when the event is fired
    MyCounterClass.CountEvent += MyCounterClass_CountEvent;
}

private void MyCounterClass_CountEvent(int c)
{
    if (textBox1.InvokeRequired)
    {
        textBox1.BeginInvoke(new Action(() => textBox1.Text = c.ToString()));
    }
    else
    {
        textBox1.Text = c.ToString();
    }
}

Since we don't want the GUI to freeze when it is counting we will use a System.Threading.Thread to count in the background and transmit the data via the event. Now this will lead to problems, because the textBox1 is created by the main thread and if you try to access it through another one it will crash. So you need to use the BeginInvoke method to display the counting variable which is transmitted via the event.

The only thing that is left is to implement the counting method. As you see I removed the static keyword. Because that would make it necessary to declare the event also as static and that would mean that it exist only one time. This will lead to difficulties if you would try to subscribe to this event from a second class.

Not we take your loop and put it in a Thread and let the thread run. At each iteration it will fire the event and transmit your counting data:

public void MyCountMethod(int interval_msec, int start, int end)
{
    System.Threading.Thread t = new System.Threading.Thread(() =>
    {
        for (int i = start; i <= end; i++)
        {
            // Check whether some other class has registered to the event
            if (CountEvent != null)
            {
                // fire the event to transmit the counting data
                CountEvent(i);
                System.Threading.Thread.Sleep(interval_msec);
            }
        }
    });
    // start the thread
    t.Start();
}

To start the method is the easiest part. Just specify the interval, start and end and call the method as you would call a normal method:

private void button1_Click(object sender, EventArgs e)
{
    MyCounterClass.MyCountMethod(300, 0, 10);
}

Et voilà, you have a class which can count up and signal the counting progress. And it is independent of the graphical user interface. It has to dependencies on Form1. And every class remains taking care of their own responsibilities. Hope it helps

Community
  • 1
  • 1
Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
  • 1
    @Default I have incorporated your comment. Thank you for the comment – Mong Zhu Feb 20 '17 at 14:00
  • Thank you @Mong Zhu for your response. I really appreciate the way you have explained it here. – mehta ankit Feb 21 '17 at 04:38
  • [Mong Zhu](http://stackoverflow.com/users/5174469/mong-zhu) So the first method is working as expected. i.e. method in one class and calling in other. But as you said we should not use it. Now in the second method which you prefer to use does not have my query as expected because everything is happening in the same class. Can you put some effort and suggest me how we can achieve the same using my expected method. i.e. call a method somewhere else and print somewhere else. By somewhere else I mean another class. @MongZhu – mehta ankit Feb 21 '17 at 04:39
  • @mehtaankit I put some effort into and made an edit. The last option is basically the advanced version of how to process data and display it on a WinForms GUI using threads and events. If my answer helped you, then you might consider accepting it by clicking the grey checkmark so it can become green ;) – Mong Zhu Feb 21 '17 at 08:16
  • where is the code for button click? I mean the counter was supposed to work on button click? – mehta ankit Feb 21 '17 at 08:52
  • @mehtaankit I added the button click event. But that you could have figured out on your own?=! ;) – Mong Zhu Feb 21 '17 at 09:00
  • Sorry but I am a bit frustrated – mehta ankit Feb 21 '17 at 09:17
  • UNHANDLED EXCEPTION WHEN i CLICK THE BUTTON. rEMEMBER WE HAVE TWO CLASS. fORM1 AND cLASS1. mYMETHOD IS IN CLASS 1 BUT ONCLICK IN FORM1? IS IS RIGHT? @MongZhu – mehta ankit Feb 21 '17 at 09:26
  • @mehtaankit yes that is right. `button1_Click` is in `Form1` and `MyCountMethod` is in `Class1`. What does the exception say? it has a message and a name – Mong Zhu Feb 21 '17 at 09:28
  • An unhandled exception of type 'System.NullReferenceException' occurred in WindowsFormsApplication2.exe Additional information: Object reference not set to an instance of an object. – mehta ankit Feb 21 '17 at 09:31
  • @mehtaankit you probably forgot to instantiate the `Class1` variable. Do you have this line: `MyCounterClass = new Class1();` in the load event or the constructor of the `Form1`? – Mong Zhu Feb 21 '17 at 09:34
  • yes I have that line and it shows that their is no constructor that accepts zero parameters. – mehta ankit Feb 21 '17 at 09:37
  • @mehtaankit In my answer I wrote: "I removed the instance of Form1 _form from Class1. Because you don't need it for the task. Also this makes your Class1 independent of the implementation of the GUI." So you need either e second constructor without parameters. Or if you want to still keep the `Form1 _form1` variable then make it `MyCounterClass = new Class1(this);` as you called it in your old program – Mong Zhu Feb 21 '17 at 09:41
  • @mehtaankit I copy pasted your edited code and it works perfectly without any exceptions. Are you sure you are running that code? – Mong Zhu Feb 21 '17 at 09:59
  • yes @MongZhu I am using the exact same code. Can you zip archive your code and upload? or any other alternative to share the code? – mehta ankit Feb 21 '17 at 10:00
  • @mehtaankit at which line do you still get the `NullReferenceException` ? I cannot send you the code. You have already the working code posted. The error is somewhere else. – Mong Zhu Feb 21 '17 at 10:10
  • this line -> MyCounterClass.MyCountMethod(300, 0, 10); – mehta ankit Feb 21 '17 at 10:36
  • it also says error in this line: MyCounterClass = new Class1(); when I add this as a parameter. – mehta ankit Feb 21 '17 at 11:35
  • @mehtaankit it might be that there is still old code mixed with new. Here is the entire working code: https://codedump.io/share/TS5M2zPDRVO6/1 – Mong Zhu Feb 21 '17 at 11:38
  • Their is still the exception. Check the screen grab @Mong Zhu [ScreenGrab](https://img42.com/uV3Dh) – mehta ankit Feb 21 '17 at 12:50
1

Maybe think about an event?

namespace WindowsFormsApplication2 {

public partial class Form1 : Form

    {
        public Form1()
        {
            InitializeComponent();
            /*_enemy = new Class1(this);
            int y = Class1.MyMethod(0);
            textBox1.Text = Convert.ToString (y);*/
        }
        private Class1 _enemy;

        private void button1_Click(object sender, EventArgs e)
        {
            _enemy = new Class1(this);
            _enemy.LoopInteration += OnLoopInteration;
            _enemy.MyMethod();
            _enemy.LoopInteration -= OnLoopInteration;
        }

        private void OnLoopInteration(object sender, LoopCounterArgs e)
        {
            textBox1.Text = Convert.ToString(e.Iteration);
        }
    }
}

second form:

namespace WindowsFormsApplication2
{
    public class Class1    
    {    
        public event EventHandler<LoopCounterArgs> LoopInteration;

        public Class1( Form1 form )
        {
            _form1 = form;
        }

        public void MyMethod()
        {
            for (int j = 1; j <= 20; j++)
            {
                LoopInteration?.Invoke(this, new LoopCounterArgs(j));
                //Thread.Sleep(100);
            }
        }
    }

    // DON'T initialize this with new Form1();
    private Form1 _form1;
}

then, new class to handle the custom event args:

namespace WindowsFormsApplication2
{
    public class LoopCounterArgs : EventArgs
    {
        public int Iteration { get; set; } 

        public LoopCounterArgs(int iteration)
        {
            Iteration = iteration;
        }
    }
}

I haven't tested this so may contain some bugs, but should be pretty much there...

You might want to re-think the textBox1.Text statement as it'll work that quickly, the value may appear as 20, when in fact it has done all the iterations for you.

Scott
  • 180
  • 6
  • thank you for your valuable time and response. Unfortunately I am getting an error in this line of code: LoopInteration?.Invoke(this, new LoopCounterArgs(j)); There shows a syntax error saying expeted ':' – mehta ankit Feb 21 '17 at 09:35
  • Remove: // DON'T initialize this with new Form1(); private Form1 _form1; && _form1 = form; I have just created a WIndows app with the lines removed and it works - Heres an example: https://1drv.ms/u/s!AjKkamHEU-ahibRrvB9-r2xaLfW1Bg – Scott Feb 21 '17 at 17:11