0

Deleted post and have code:

namespace StackOverflowQuestion
{
  class Program
  {
        static void Main (string[] args)
        {
              var cw = new ConsoleWriter ();

              for (int i = 0; i <= 5000000; i++)
              {
                    cw.Write (i);
              }

              Console.ReadKey ();
        }
  }

  class ConsoleWriter
  {
        private Stopwatch sw = new Stopwatch ();

        public ConsoleWriter ()
        {
              sw.Start ();
        }

        public void Write (int pNumber)
        {
              if (sw.ElapsedMilliseconds >= 50)
              {
                    Console.WriteLine (pNumber);
                    sw.Restart ();
              }
        }
  }
}

And the output: 305940
651171
1002965
1358665
1715740
2069602
2419054
2772833
3127880
3485054
3844335
4204016
4557912
4913494

So everything works fine. In this example ConsoleWriter display number on console, but it could be displayed in Control surface. Like you see, even if I call 5000000 times Write method, it only updates UI after minimum 50 ms. Great, but notice in many cases the last value 5000000 will not be displayed. How to fix it? Should I use a class (thread) which will call event each 50 ms and it will check the value to write is changed?

apocalypse
  • 5,764
  • 9
  • 47
  • 95

2 Answers2

4

You could use a timer

Timer _timer;

public void StartTimer()
{
    _timer = new Timer();
    _timer.Interval = 100; // 100 ms = 0.1 s
    _timer.Tick += new EventHandler(timer_Tick);
    _timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
    myControl.Number = i;
}

In the control there should be something like

private int _number;    
public int Number
{
    get { return _number; }
    set
    {
        if (value != _number) {
            _number = value;
            Invalidate();
        }
    }
}

The call of Invalidate() will trigger the Paint event. Your painting logic should be in the OnPaint method:

protected override void OnPaint(PaintEventArgs e)
{
    ... paint here
}

But of cause the for loop itself will freeze the application. You could use a second timer that updates the counter at a faster intervall than your display counter. Every code running on the UI-tread (main thread if you prefer) will freeze you UI until it terminates. An easy way of doing a heavy work in the background in a separate thread is to use the BackgroundWorker. The background worker automatically switches between UI-thread and worker-thread and allows you to report progess to the UI-thread.

You can also start a thread manually in order to update the counter. If this will be the only thread changing the number, no synchronisation mechanisms will be required. But never access the UI (a form or a control) from another thread than the UI-thread.

Here is a complete non-blocking solution using another thread for the counting

Timer _timer;
int _counter;
System.Threading.Thread _worker;

public frmTimerCounter()
{
    InitializeComponent();
    _worker = new System.Threading.Thread(() =>
    {
        while (_counter < 10000000) {
            _counter++;
            System.Threading.Thread.Sleep(20);
        }
    });
    _worker.Start();
    StartTimer();
}

public void StartTimer()
{
    _timer = new System.Windows.Forms.Timer();
    _timer.Interval = 100; // 100 ms = 0.1 s
    _timer.Tick += new EventHandler(timer_Tick);
    _timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
    // I used a Label for the test. Replace it by your control.
    label1.Text = _counter.ToString();
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Thanks for a code, I thought about timer too. Now I just think what timer I need to use to get good precision for smooth visualisations. But the problem is I need to put timer logic in a Control. Only idea I have is make a class like TimerProvider (on new thread) and every control will be registering self in this class. So this TimerProvider will call repaint method in some perioid of time to each registered control. When I use if(value != numer) it will not waste cpu resources. Is this good idea? – apocalypse Jan 08 '13 at 21:26
  • 1
    You could use a timer inside your control and test whether the value changed before displaying it in the timer event handler. `if(Number != _oldNumber) { _oldNumber = Number; Invalidate(); }`. – Olivier Jacot-Descombes Jan 08 '13 at 21:48
  • Thanks, with your code I think Im able to solve my problem. – apocalypse Jan 08 '13 at 22:24
2

You didn't post any code but I can guess at what it looks like. You are doing way too much work in the property setter. This for() loop should never take more than a millisecond, way too short to ever notice a GUI freeze.

You get this by following the standard way controls repaint themselves. Which is lazily. You get that by calling the Invalidate() method. Like this:

class MyControl : Control {
    private int number;
    public int Number {
        get { return this.number; }
        set {
            if (value != this.number) this.Invalidate();
            this.number = value;
        }
    }
    protected override void OnPaint(PaintEventArgs e) {
        // TODO: paint number
        //...
        base.OnPaint(e);
    }
}

You'll now also discover something else, there's no point to using that for() loop anymore. There never was one in the first place, a human being cannot possibly see the incredible rate at which a modern processor can increment a number. So you'll now replace that with:

myControl.Number = 50000;

If you actually meant for a human eye to see the number increasing then you are going to have to do it a lot slower. No more than about once every 50 millisecond, about the point where the changes turn into a blur. That requires a Timer.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536