0

I have started a typical windows forms project (c#) with visual studio. I'm using a BackgroundWorker to fill up a TreeView control and display current progress for user. I have to use a Control.Invoke method to get access for the my TreeView control's methods (like TreeView.Nodes.Add(string ...) ). I have two questions.

Is it possible to "automatically" get reference to the object which invoke delegate method? For example, when I call myTree.Invoke(tbu, new object[] {myTree}) , I send a myTree object as an argument for the method. Is it the only possible way or I can do it in a someway like EventHandlers do (like an "Object sender" argument)?

And what is the best practice: to declare a class method used for delegate as static (TreeBU in this code), or as I have done below - Declare a static public variable for MainForm object and then use it when initialize a delegate object ( TreeStart tbu = Program.thisForm.TreeBU )?

Sorry for my c# and english, and thanks in advance!

namespace SmartSorting
{
    public delegate void TreeStart(TreeView xmasTree);

    static class Program
    {
        public static MainForm thisForm;

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            thisForm = new MainForm();
            Application.Run(thisForm);
        }
    }

    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            treeView1.Nodes.Clear();
            backgroundWorker1.RunWorkerAsync(treeView1);
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker1 = (BackgroundWorker) sender;
            e.Result = stage1(worker1, (TreeView)e.Argument);
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null) MessageBox.Show(e.Error.Message);
        }

        private bool stage1(BackgroundWorker wrkr, TreeView myTree)
        {
            TreeStart tbu = Program.thisForm.TreeBU;
            myTree.Invoke(tbu, new object[] {myTree});
            return true;
        }

        public void TreeBU (TreeView xmasTree)
        {
            xmasTree.BeginUpdate();
        }
    }
}
Eugenii10
  • 5
  • 2
  • The rough guidance that if you use Control.Invoke() then you are doing it wrong. Trying to get data from the UI in a worker thread is never correct, that data can randomly change when the user continues to interact with the UI while your code is running. Simply obtain the data *before* you start the worker, pass it as an argument to RunWorkerAsync(). Now you don't have randomly changing data *and* you don't need Invoke anymore. – Hans Passant Jan 01 '14 at 13:13
  • It's a bit different situation. I'm trying to change the form's control, instead of reading data from it. And even if the TreeView had been passed as an argument for RunWorkerAsync(), I cannot call it's methods from the worker's thread - I get an exception. – Eugenii10 Jan 02 '14 at 03:59

1 Answers1

0

You usually assign a delegate by directly passing it a function (which must match the delegate signature!):

MyCrossThreadDelegateInstance += invokeMe;

or

new MyCrossThreadDelegate(invokeMe);

Check this: Youre on different thread and would like to update the TreeControl using your invokeMe() method.

private void invokeMe()
{
    MyTree.BeginUpdate();
}

Due to this call on MyTree.BeginUpdate() is coming from a different thread, crossthread exception is thrown. To prevent this we modify our invokeMe() method to avoid throwing the exception:

private void invokeMe()
{
    if (MyTree.InvokeRequired)
        MyTree.Invoke(new CrossThreadDelegate(invokeMe);
    else
        MyTree.BeginUpDate();
}

Before invoking u check if invoke is required - this is the case when u try to access a control from a different thread then the one the control was created on. This way it tries to find the thread which owns and created the control by bubbling up you thread tree. If Control.InvokeRequired returns true, the same method (passed over by the delegate) is called again from the next thread. This is repeated until the owning thread is found. Now Control.InvokeRequired returns false and your ELSE-block is executed on the proper thread whithout throwing a crossthread exception. For more details see MSDN Control.Invoke

There is no need to declare anything static except you want your delegate to be available in a global scope.

Edit: If you would use the BackgroundWorker like it was meant to be, the ProgressChanged event would do the job since this event is risen on the proper thread (UI thread). This event is fired by calling the BackgroundWorker.ReportProgress() member. See MSDN - BackgroundWorker class for more details

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks! I rewrote code according to your suggestion, and it is now working without static members. And about ProgressChanged event: I use it for ProgerssBar state updating, while populating the TreeView in a worker thread through invoking. – Eugenii10 Jan 02 '14 at 04:08