0

I'm trying to learn c# at the momemnt.

My problem is:

I have 2 classes. MainWindow.xaml.cs for the UI and another one called dosomething.cs.

If a button on the UI is clicked a method from dosomething.cs should be called. That just works fine. I want that dosomething.cs while running the click-handler (takes some time) tell the actual state to the UI.

status_textblock = "test";
for (int i = 1; i<1000000; i++)
i += i;
Thread.sleep(100);
Status_textblock = i.tostring();
}

What do I have to do that every new value immediately appears in the UI? I already tried Backgroundworker and Dispatcher with no success.

Tomtom
  • 9,087
  • 7
  • 52
  • 95

3 Answers3

1

I would use a BackgroundWorker here. In the function in dosomething.cs you start the worker and your UI-CodeBehind subscribes for an event of dosomething.

Small example:

UI-Codebehind:

private void ButtonClick(object sender, ...)
{
   var doSomething = new DoSomething();
   doSomething.StatusChanged += OnStatusChanged;
   doSomething.Do();
}

private void OnStatusChagned(string state)
{ 
   // Update the UI with the state 
}

DoSomething

public class DoSomething
        {

            public void Do()
            {
                var worker = new BackgroundWorker();
                worker.WorkerReportsProgress = true;
                worker.DoWork += WorkerOnDoWork;
                worker.ProgressChanged += WorkerOnProgressChanged;
                worker.RunWorkerAsync("Paremeter to pass to the BackgroundWorker");
            }

            private void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                var temp = StatusChanged;
                if (temp != null)
                    temp((string) e.UserState);
            }

            private void WorkerOnDoWork(object sender, DoWorkEventArgs e)
            {
                var worker = (BackgroundWorker) sender;
                // If you want to get the passed argument you can call
                var parameter = e.Argument;

                for (int i = 0; i < 100000; i++)
                {
                    // do your background-stuff here
                    // ...
                    // Notify the UI
                    worker.ReportProgress(0, "STATUS-OBJECT");
                }
            }

            public delegate void OnStatusChanged(string status);

            public event OnStatusChanged StatusChanged;
        }
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Tomtom
  • 9,087
  • 7
  • 52
  • 95
1

Problem 1: Reference to the MainWindow.cs:

In object oriented programming you decouple the components for better overview, testability inheritance, etc... But you have to think about how you propagate the reference between the objects. What i mean is: the instance of class dosomething has to have a reference to the MainWindow to be able to update anything on it. What you do now is: you create a new instance of MainWindow in the method and change the status (but not of the MainWIndow which is in reality visualized)

You can pass a reference to the actual MainWindow to the function. This is called Dependency Injection. Normally you would inject only an interface which is implemented by the MainWindow class, so also another class could use this functionality, but this is leading too far. Quick fix:

public void Do(MainWindow window)
{
    window.status_textblock = "test";
    for (int i = 1; i<1000000; i++)
    i += i;
    Thread.sleep(100);
    window.status_textblock = i.tostring();
}

and call it on the MainWindow like that:

new Dosomething().Do(this);

But this will still not work, as the Thread is blocked (with Thread.Sleep) and will only visualize the results at the end when the Thread is free to render the Visualization.

Problem 2: You have to run it in another Thread / Task

public void Do(MainWindow window)
{
    Task.Factory.StartNew(() => {
        window.status_textblock = "test";
        for (int i = 1; i < 1000000; i++)
            i += i;
        Thread.sleep(100);
        window.status_textblock = i.tostring();
    });
}

but wait this will not work either, because you try to update the UI from another thread which is not allowed

Problem 3: Update the UI with the STA thread

In the MainWindow class you create a method which updates the status text on the STA thread (STA thread is the only one which is allowed to update anything on the UI).

public void UpdateStatus(string status)
{
    if (this.Dispatcher.CheckAccess())
        this.status_textblock = status;
    else
        this.Dispatcher.Invoke(new Action<string>(UpdateStatus), status);
}

and from the DoSomething class you call the UpdateStatus Method:

public void Do(MainWindow window)
{
    Task.Factory.StartNew(() => {
        window.UpdateStatus("test");
        for (int i = 1; i < 1000000; i++)
            i += i;
        Thread.sleep(100);
        window.UpdateStatus(i.tostring());
    });
}

--> it works!

fixagon
  • 5,506
  • 22
  • 26
0

Using a Backgroundworker is recommended in more posts on this subject see here for example. What you do is:

  1. When the button is clicked initiate a new Backgroundworker

  2. Bind to the following events: DoWork and ProgressChanged

  3. In the DoWork delegate you can call ReportProgress with a custom object or just a single integer

  4. The method bound to ProgressChanged will fire on the UI thread and there you can update a label or anything you like.

  5. Maybe you should "refresh" the page / view with the answers in this post.

    For more information on Backgroundworker take a look here.

Community
  • 1
  • 1
Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59