Frage

I am working on a MDI app where the user can create multiple instances of the same form (call it ListForm). Each instance of the ListForm has a flowlayoutpanel containing a unique set of user controls. The ListForm also contains a StatusStrip ProgressBar and a button called 'ReadAll'.

Each user control has a 'Read' button that will perform a read operation when clicked. This operation can take up to 3 seconds to complete.

What I am trying to do is when the user clicks the 'ReadAll' button, the childform spawns a background thread the iterates through the flowlayoutpanel.controls collection and invokes each user controls .PerformClick() method. This updates all the usercontrols in the form.

The problem is that it looks like the event handler for all instances of the form is being called resulting in all user controls in all instances of the ListForm are being updated. Additionally, when I ReportProgress from the backgroundworker, all the progressbars for all instances of the ListForm are updated. This functionality is not desired.

How can I ensure that only the ListForm that spawned the backgroundworker is updated? Is there a preferred way to uniquely identify the child form?

Thanks in advance for your help. Code is below...

public partial class ListForm: Form
{
    // Background Worker Thread for Read / Write All tasks
    private static BackgroundWorker bw = new BackgroundWorker();

    public ListForm()
    {
        InitializeComponent();

        // Configure the Background Worker that reads and writes all variable data
        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);


    }

    private void btnReadAll_Click(object sender, EventArgs e)
    {

        if (bw.IsBusy != true)
        {
            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync("R");
        }
        else if (bw.WorkerSupportsCancellation == true)
        {
            // Cancel the ReadAll parameters thread
            bw.CancelAsync();
        }
    }

    // ******************************  Background Thread Methods ***************************
    public delegate void DoUIWorkHandler();

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        DoUIWorkHandler DoReadClick;
        DoUIWorkHandler DoWriteClick;

        int CurrentControlCount = 1;
        string StatusText = "";
        int ProgressValue = 0;
        string argument = e.Argument as string;

        // *******************Perform a time consuming operation and report progress. 
        try
        {
            foreach (UserControl c in this.flowLayoutPanel1.Controls)
            {
                if ((worker.CancellationPending == true))
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    // Update the status and return it to the UI
                    StatusText = "Updating: (" + (CurrentControlCount).ToString() + " of " + flowLayoutPanel1.Controls.Count.ToString() + ") " + c.ParamProperties.strDHIndexDescription;
                    ProgressValue = (int)(((float)CurrentControlCount / (float)flowLayoutPanel1.Controls.Count) * 100);
                    worker.ReportProgress(ProgressValue, StatusText);
                    System.Threading.Thread.Sleep(20);
                    CurrentControlCount++;

                    // Update the contorl
                    if (c.InvokeRequired)
                    {
                        if (argument == "R")
                        {
                            DoReadClick = c.btnRead.PerformClick;
                            c.Invoke(DoReadClick);
                        }
                        else
                        {
                            DoWriteClick = c.btnWrite.PerformClick;
                            c.Invoke(DoWriteClick);
                        }

                    }
                }
            }
        }
        catch(InvalidCastException ex)
        {
            // Catch any functions that are in the Layout panel
            string ErrorStr = "Could not cast a Function control to a Parameter control. \n\r\r Exception: " + ex.Message;
            srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
        }
        catch (Exception ex)
        {
            string ErrorStr = "An unecpected exception occured. Error: " + ex.Message.ToString();
            srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
        }
    }

    private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.tsStatus.Text = e.UserState.ToString();
        this.tsProgressBar.Value = e.ProgressPercentage;
    }
War es hilfreich?

Lösung

You've one instance of BackgroundWorker and each ListForm you create, is registered to this worker. So you've to pass the instance of the Form to the worker.

Create a little Helper class with two Attributes. This is just an example. You could also pass an identifier or what ever you like:

public struct ReadAllArguments
{
    public bool Read;
    public ListForm CallingForm;

    public ReadAllArguments(bool read, ListForm callingForm)
    {
        Read = read; CallingForm = callingForm;
    }
}

You could pass it then like this:

...
    if (bw.IsBusy != true)
        {
            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync(new ReadAllArguments(true, this));
        }
...

An later read it like that:

private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        DoUIWorkHandler DoReadClick;
        DoUIWorkHandler DoWriteClick;

        int CurrentControlCount = 1;
        string StatusText = "";
        int ProgressValue = 0;
        ReadAllArguments arguments = e.Argument as ReadAllArguments;
        if (this != arguments.ListForm)
          return;

        ...

                        if (arguments.Read)
                        {
                            DoReadClick = c.btnRead.PerformClick;
                            c.Invoke(DoReadClick);
                        }
                        else
                        {
                            DoWriteClick = c.btnWrite.PerformClick;
                            c.Invoke(DoWriteClick);
                        }
       ...

You'll realize that you can even move the Work-Method out of you Form because there are no direct dependencies and you don't need access to the "this"-Qualifier. You've passed everything in you argument. After replacing every "this" by that argument you could register exactly one Work-Method to the DoWork-Event of your Worker. This would be much cleaner and more elegant...

Here's an example how you could do this:

public partial class ListForm: Form
{
    // Background Worker Thread for Read / Write All tasks
    private static BackgroundWorker bw = new BackgroundWorker();

    static ListForm()
    {
        //We move the do-work out of the instance constructor, because the work that has to be done, is not connected to our instances. So we've only one definition of our work that has to be done
        bw.DoWork += new DoWorkEventHandler(TheWorkThatHasToBeDone);
    }

    public ListForm()
        {
             InitializeComponent();

             // Configure the Background Worker that reads and writes all variable data
             bw.WorkerReportsProgress = true;
             bw.WorkerSupportsCancellation = true;
             //no more registering on instance level 
             bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
             bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);    
        }

    //Your new instance-independent doWork-Method - static here
    private static void TheWorkThatHasToBeDone(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        ReadAllArguments arguments = e.Argument as ReadAllArguments;
        //You call the instance-Method here for your specific instance you want the work to be done for
        arguments.ListForm.bw_DoWork(worker, arguments);
    }


    //Your old bw_DoWork-Method with nicer arguments - you should change the method name...
    private void bw_DoWork(BackgroundWorker worker, ReadAllArguments arguments)
        {
            DoUIWorkHandler DoReadClick;
            DoUIWorkHandler DoWriteClick;

            int CurrentControlCount = 1;
            string StatusText = "";
            int ProgressValue = 0;

            // *******************Perform a time consuming operation and report progress. 
            try
            {
                ...
            }
        }

It would again be more elegant to move the stuff out of the forms code and not doing this with static members, but I think the idea is clear.

Andere Tipps

To identify object you could use HashCode or create an Id property, and next use it in custom EventArgs.

private Guid _controlId;

    public ListForm()
    {
        _controlId = Guid.NewGuid();
        ...
    }

Try also to menage the event observators in this way:

private void btnReadAll_Click(object sender, EventArgs e)
    {
        if (bw.IsBusy != true)
        {
            bw.DoWork += bw_DoWork;
            bw.ProgressChanged += bw_ProgressChanged);
            bw.RunWorkerCompleted +=bw_RunWorkerCompleted;

            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync("R");
        }
        else if (bw.WorkerSupportsCancellation == true)
        {
            // Cancel the ReadAll parameters thread
            bw.CancelAsync();
        }

        bw.DoWork -= bw_DoWork;
        bw.ProgressChanged -= bw_ProgressChanged;
        bw.RunWorkerCompleted -= bw_RunWorkerCompleted;
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top