views:

117

answers:

2

Edit: Got somewhere, I hope

Here is what I've got, but I'm not so sure about how I delegate my bcLoad.ReportProgress(i) to the object created (ie how to make the delegate so it can be passed). I've created the object events which work sort of(I can call my object method and I can see a change triggered when reading in lines). I know when objectChanged is working(written to console). However, bcLoad_RunWorkerCompleted doesn't seem to work, the code in the if statement is never executed so I'm going wrong somewhere. The file loads though.

Could someone please possible set out how to create the delegate, then which section to use the pass delegate in(I assume in the object) and why bcLoad_RunWorkerComplete is null. This is really the first time I've used events, delegates and backgroundworkers in c#

/*
The object which does file operations
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Windows.Forms;
using System.ComponentModel;
using System.Data;

namespace aodProductionViewer
{
    public class fileOperationsSpecial
    {
        public event EventHandler Changed;

        protected virtual void OnChanged(EventArgs e)
        {
            if (Changed != null)
            {
                Changed(this, e);
            }
        }

        public fileOperationsSpecial() 
        {        }

        /// <summary>
        /// Count the number of lines in the file specified.
        /// </summary>
        /// <param name="f">The filename to count lines in.</param>
        /// <returns>The number of lines in the file.</returns>
        static long CountLinesInFile(string f)
        {
            long count = 0;
            try
            {
                using (StreamReader r = new StreamReader(f))
                {
                    string line;
                    while ((line = r.ReadLine()) != null)
                    {
                        count++;
                    }
                }
            }
            catch (Exception err)
            {
                string strTemp = "Error get number of lines for save game file. \n" +
                                err.ToString();
                errorDialog errDiag = new errorDialog("save game line count",
                                        strTemp, true);
            }
            return count;
        }

        /// <summary>
        /// Use this to readin in a file
        /// </summary>
        /// <param name="strPath">Path of file to read in</param>
        /// <returns>a string array of the file</returns>
        public string[] readFile(string strPath)
        {
            long lng_LineCount = CountLinesInFile(strPath);
            string[] strReadIn = new string[lng_LineCount];
            try
            {
                long lngCount = 0;
                using (StreamReader reader = new StreamReader(strPath))
                {
                    String line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        strReadIn[lngCount] = line;
                        lngCount++;
                        OnChanged(EventArgs.Empty);
                    }
                }

            }
            catch (Exception err)
            { //            
            }

            return strReadIn;
        }
   }
}

/*
Event Listner
*/
using System;
using System.Collections.Generic;
using System.Text;

namespace aodProductionViewer
{
    class EventListener
    {
        private fileOperationsSpecial FPS;

        public EventListener(fileOperationsSpecial _fps)
        {
            FPS = _fps;
            FPS.Changed += new EventHandler(objectChanged);
        }

        private void objectChanged(object sender, EventArgs e)
        {            //changed has occured
        }

        public void Detach()
        {
            FPS.Changed -= new EventHandler(objectChanged);
            FPS = null;
        }
    }
}

/*
The backgroundWorker code (Part of)
*/

    BackgroundWorker bcLoad = new BackgroundWorker();

    private void btt_load_save_game_Click(object sender, EventArgs e)
    {
        //Do some file dialog stuff
        string strPath = null;
        bcLoad.RunWorkerAsync(strPath);
    }

    void bcLoad_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        tb_ProgressBar.Value = e.ProgressPercentage;
    }

    void bcLoad_DoWork(object sender, DoWorkEventArgs e)
    {
        string strPath = e.Argument as string;
        fileOperationsSpecial FPS = new fileOperationsSpecial();
        EventListener listener = new EventListener(FPS);
        string strArray = FPS.readFile(strPath);
        listener.Detach();
    }

    void bcLoad_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Result != null)
        {
            //Everything done
            tb_ProgressBar.Visible = false;
        }
    }

I've got the gist of using a BackgroundWorker to do some work and update the UI for progress and completion, I've got something simple at the moment As you can see, I pass a string (which is a path) I would then read a file in and update a progress, currently I'm just sleeping the thread and setting the progress for the sake of a demo. I also intend to return an object (string array) but I've yet to get around to that.

Now my question is, how can I do all this in an object created by my form and still update my UI? I have an object which currently does operations on files (ie read file, write, get info).

Currently as my understanding stands the demo below goes Form > BackgroundWorker > Update form for progress.

I would like it to go

Form > Create Object > BackgroundWorker > Update form for progress > return string array

I've looked about and not made heads or tails of any of the examples, so I thought I'd ask people who would know. Is this even possible? What I'm trying to aim for is to remove any file processing out of my form so it becomes easier to manage and maintain.

Full code example on how to do this would be fantastic!

Here is what I understand so far (remember just for an examples sake, this won't compile)

BackgroundWorker bcLoad = new BackgroundWorker();

    public frm_ProductionViewer()
    {
        InitializeComponent();
        load_settings();
        bcLoad.DoWork += new DoWorkEventHandler(bcLoad_DoWork);
        bcLoad.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bcLoad_RunWorkerCompleted);

        bcLoad.WorkerReportsProgress = true;
        bcLoad.ProgressChanged += new ProgressChangedEventHandler(bcLoad_ProgressChanged);

        bcLoad.WorkerSupportsCancellation = true;
    }

private void btt_load_save_game_Click(object sender, EventArgs e)
    {

        ts_label_GameLoaded.Text = "Loading";
        bcLoad.RunWorkerAsync(strPath);
    }

void bcLoad_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        tb_ProgressBar.Value = e.ProgressPercentage;
    }

    void bcLoad_DoWork(object sender, DoWorkEventArgs e)
    {
            string strPath = e.Argument as string;
            //load file
            //Update progress
            bcLoad.ReportProgress(80);
            Thread.Sleep(300 * 5);
            bcLoad.ReportProgress(100);
    }

    void bcLoad_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Result != null)
        {
            textBox1.Text = "done";
        }
        tb_ProgressBar.Visible = false; ;
        ts_label_GameLoaded.Text = "Loaded";
    }
+3  A: 

Your object (that works with files) should preferably not depend on either the GUI or the Bgw.

So keep your Bgw with (on) your Form and call into the object form DoWork.

To report feedback, your Object needs an event (or the main methods taking a delegate parameter).

In the handler of that event (called on the worker thread), call the bcLoad.ReportProgress(percent).


Reaction to the Edit:

  • you need an EventArgs type with room for the Percentage, like EventHandler<ProgressEventArgs> , you probably have to write ProgressEventArgs.

  • I don't think you want a separate EventListener class. This is the Forms job. That gets you:

 

class Form ...
{
    private void objectChanged(object sender, ProgressEventArgs e)
    {   //changed has occured
        // trigger the Bgw event
        // or use Form.Invoke here to set the progress directly
        bcLoad.ReportProgress(e.Percentage);          
    }

}

So a 'change' now filters through 2 eventhandlers. 1 to break dependency from the FPS, 1 to sync to the Form.

Henk Holterman
Thanks I'm getting somewhere, I'm heading in your direction, but I'm struggling with the delegate part and then using the delegate in the object. I've edited the main post with my changes and problems.Thanks for your help :D
Shibby
A: 

If you want a class (other than the form) to encapsulate the background worker, one approach is to add an event to this class and subscribe to this event within the form:

  • Class X encapsulates background worker and publishes a "Done" event
  • Form creates instance of Class X, and event handler for "Done" event
  • Form calls Class X's instance "Do async work" method and moves on
  • Class X's instance notifies form when async work completes via Done event, passing the string or whatever state the form needs
Jorge