-1

I'm actually learning (the hard way) c# and been fighting for days with a problem :

I'm writing my first c# application with WPF (dotNet 4.0). When I click on a button, a BackgroundWorker thread is used and call a method from an external class, this way my UI don't freeze -> my method run as expected.

Then I tried to update a ListView control from thos external class to get some kind of progress (text) and I miserably failed.

I understand that I need to use a delegate and the dispatcher to update my control.

I tried to use the solution offered here How to update UI from another thread running in another class . (I cannot comment on it because of my low rep) and I miss some parts of the puzzle.

What the YourEventArgs(status) is referring to ? I just don't get the way to fire an event and pass the content back to my UI while my method is running inside the BGW.

So far I have this piece of code (Updated from answer):

namespace AppMain
{
    public partial class MainWindow
    {
        BackgroundWorker AppWorker = new BackgroundWorker();

        public MainWindow()
        {
            InitializeComponent();

            AppWorker.WorkerSupportsCancellation = true;
            AppWorker.DoWork += AppWorker_DoWork;
            AppWorker.RunWorkerCompleted += AppWorker_RunWorkerCompleted;
            }

        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
            lstTest.Items.Add("Processing data...");
            AppWorker.RunWorkerAsync();
        }

        public void AppWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            SetXmlData xml = new SetXmlData();
            xml.ProgressUpdate += (s, evt) =>
            {
                Dispatcher.BeginInvoke((Action)(() =>
                {
                    lstTest.Items.Add("this is a test : " + evt.myData); //how to retrieve the myData property from evt ?
                }));
            };
            xml.FlushData();
        }

        public void AppWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            if (!(e.Cancelled))
            {
                lstTest.Items.Add("Done");
            }
            else
            {
                MessageBox.Show("Cancelled");
            }
        }
}
}

SetXmlData.cs

namespace AppMain
{
    public class SetXmlData
    {
    public event EventHandler ProgressUpdate;

        //update method
        public void update(object input)
        {
        if (ProgressUpdate != null)
        ProgressUpdate(this, new YourEventArgs { myData = (string)input });
        }

        //calculation method
        public void FlushData()
        {
            MessageBox.Show("this is a test !");
            update("test");
        }
    }

    public class YourEventArgs : EventArgs
    {
        public string myData { get; set; }
    }
}

Thanks for your help.

Community
  • 1
  • 1
Coloris
  • 35
  • 8
  • Related, possible duplicate: http://stackoverflow.com/questions/11625208/accessing-ui-main-thread-safely-in-wpf – jdphenix Apr 20 '15 at 04:44
  • Why use BGW if you have tasks in .net 4.0, which can be provided by a current synchronization context to be executed in? – VMAtm Apr 20 '15 at 09:32
  • I was using this at first but then moved to BGW for the ReportProgress feature, at the end you are right, it's not better I'll switch back to Task when I'll sort out this issue. – Coloris Apr 20 '15 at 18:01

2 Answers2

0

You can simply Invoke the ProgressUpdate event from the FlushData() method.

Simply call:

If (ProgressUpdate !=null )
{
    ProgressUpdate(this,new YourEventArgs())
}

this is the source instance where the event originated from.

You could just create YourEventArgs by inheriting from EventArgs class.

  public class YourEventArgs : EventArgs
  {
     //Put any property that you want to pass back to UI here.
  }

When the event gets raised in the UI:

RaiseEvent.ProgressUpdate += (s, e) =>
        {
            Dispatcher.BeginInvoke((Action)(() => 
                                        { 
                                            lstTest.Items.Add("this is a test : "); 
                                            //Add items to your UI control here...
                                        }));
        };

e will be of type YourEventArgs.

On a side note, you should never touch UI thread from a diffent thread (like background worker thread in your example). Since your event-handler already does the Dispatcher.BeginInvoke, that's safe.

Also, your ProgressUpdate event should be inside of your class SetXmlData.

ANewGuyInTown
  • 5,957
  • 5
  • 33
  • 45
  • Hi ANewGuyInTown and thank you for your kind answer. I've updated my code after reading your solution. So far, I had to move the subscription inside the DoWork method, if I don't, the handler is always set to null and the event is not raised ?! Is it because I need to do it from the thread used to instantiate the SetXmlData class ? Also, I can't get the content from myData property, when I try to use evt.myData I have this : 'System.EventArgs' does not contain a definition for 'myData' and no extension method 'myData' accepting a first argument of type 'System.EventArgs' could be found – Coloris Apr 21 '15 at 01:27
  • modify your `ProgressUpdated` event hanlder to `public event EventHandler ProgressUpdate;` and yes , you have to do it in the thread where you instantiate `SetXmlData` class. – ANewGuyInTown Apr 21 '15 at 01:52
0

try get;set; Example:

Form1:

public partial class Form1 : Form
{
    static public string gettext { get; set; }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Class1.send(); //call to class function
        textBox1.Text = gettext; //items.add(gettext)
    }
}

Class1:

    class Class1
{
    static public void send()
    {
        Form1.gettext = "Marko"; //Set gettext to string "Marko"

    }
}
TheStart101
  • 76
  • 1
  • 10