2

Possible Duplicate:
How to update GUI from another thread in C#?

I've currently got a C# program to run a query and display the results in a datagridview.

The query due to size of records takes a while (20-30 seconds) to run.

I thought I would add an animation so the user at least knows the software is running and has not stopped working.

Of course I can't run anything when the call is being made to the procedure so I looked into threading.

Here is my code (forgive me, I haven't really put in comments yet):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Threading;

namespace RepSalesNetAnalysis
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            pictureBox2.Visible = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            GetsalesFigures();   
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            AutofillAccounts();
        }


        private void GetsalesFigures()
        {
            try
            {
                string myConn = "Server=herp;" +
                            "Database=shaftdata;" +
                            "uid=fake;" +
                            "pwd=faker;" +
                            "Connect Timeout=120;";

                string acct;// test using 1560
                SqlConnection conn = new SqlConnection(myConn);
                SqlCommand Pareto = new SqlCommand();
                BindingSource bindme = new BindingSource();
                SqlDataAdapter adapt1 = new SqlDataAdapter(Pareto);
                DataSet dataSet1 = new DataSet();
                DataTable table1 = new DataTable();

                Thread aniSql = new Thread(new ThreadStart(animateIcon));//CREATE THE THREAD


                acct = accCollection.Text;

                string fromDate = this.dateTimePicker1.Value.ToString("MM/dd/yyyy");
                string tooDate = this.dateTimePicker2.Value.ToString("MM/dd/yyyy");

                Pareto.Connection = conn;
                Pareto.CommandType = CommandType.StoredProcedure;
                Pareto.CommandText = "dbo.GetSalesParetotemp";
                Pareto.CommandTimeout = 120;

                Pareto.Parameters.AddWithValue("@acct", acct);
                Pareto.Parameters.AddWithValue("@from", fromDate);
                Pareto.Parameters.AddWithValue("@too", tooDate);

                aniSql.Start();                //START THE THREAD!
                adapt1.Fill(dataSet1, "Pareto");
                aniSql.Abort();                //KILL THE THREAD!
                //pictureBox2.Visible = false;

                this.dataGridView1.AutoGenerateColumns = true;
                this.dataGridView1.DataSource = dataSet1;
                this.dataGridView1.DataMember = "Pareto";

                dataGridView1.AutoResizeColumns(
                    DataGridViewAutoSizeColumnsMode.AllCells);

            }
            catch (Exception execc)
            {
                MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
                            + " information:\n\n" + execc.Message + execc.StackTrace,
                            "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }

        }

        private void AutofillAccounts()
        {
            //get customers list and fill combo box on form load.
            try
            {
                string myConn1 = "Server=derp;" +
                                "Database=AutoPart;" +
                                "uid=fake;" +
                                "pwd=faker;" +
                                "Connect Timeout=6000;";
                SqlConnection conn1 = new SqlConnection(myConn1);
                conn1.Open();
                SqlCommand accountFill = new SqlCommand("SELECT keycode FROM dbo.Customer", conn1);

                SqlDataReader readacc = accountFill.ExecuteReader();

                while (readacc.Read())
                {
                    this.accCollection.Items.Add(readacc.GetString(0).ToString());
                }
                conn1.Close();
            }
            catch(Exception exc1)
            {
                MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
                            + " information:\n\n" + exc1.Message + exc1.StackTrace,
                            "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }
        public void animateIcon()
        {
            // animate
            pictureBox2.Visible = true;  
        }
    }
}

As you can see I want to run the animation just before the procedure call and then end it just after.

My knowledge on threads is brand new. I've looked around but i'm getting a little confused at the moment.

Here's my error:

Thrown: "Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on." (System.InvalidOperationException) Exception Message = "Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.", Exception Type = "System.InvalidOperationException"

I need a very simple way of performing an animation while my sql proc is reading.

Something like picture.visible = true when its started and false when it ends.

Community
  • 1
  • 1
lemunk
  • 2,436
  • 10
  • 54
  • 84

5 Answers5

4

Invoke is needed if you want to do this.

          private delegate void InvokeDelegate();

          public void DoSomething()
          {
               if (InvokeRequired)
               {
                    Invoke(new InvokeDelegate(DoSomething));
                    return;
               }
               // dosomething
          }

you can also add variables to the delegate and use them:

      private delegate void InvokeDelegate(string text);
      public void DoSomething(string text)
      {
           if (InvokeRequired)
           {
                Invoke(new InvokeDelegate(DoSomething), text);
                return;
           }
           // dosomething with text
      }

hope this helps :).

stefan

Stefan Koenen
  • 2,167
  • 2
  • 17
  • 31
  • Just a little remark about `InvokeRequired`, sometimes it can lie to you as explained here: http://www.ikriv.com/en/prog/info/dotnet/MysteriousHang.html. If your application hangs, at least you'll know where to look first ^_^ – JiBéDoublevé Nov 24 '11 at 20:46
1

You need to use InvokeRequired to access/modify a control from a thread other than the main thread of your form. Documentation here: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx

David
  • 68,722
  • 16
  • 125
  • 165
1

As others have pointed out, you cannot perform UI-related operations on a separate thread.

If you want your application to be responsive, you should perform the data operation on a separate thread instead.

If you just want to show the PictureBox control, you don't need the extra thread at all:

pictureBox2.Visible = true;
pictureBox2.Refresh(); // <-- causes the control to be drawn immediately
...large operation...
pictureBox2.Visible = false;

However if the user for instance alt-tabs back and forth, or drags another window over yours, the application would seem to hang as the UI thread is busy performing the data operation.

I'm suprised that so many people advise you to keep your current code and use InvokeRequired and Invoke, even though Invoke will only execute when the UI thread has time to process it (AFTER the data operation).

C.Evenhuis
  • 24,516
  • 2
  • 54
  • 68
  • yeh, plus has it goes into the sql procedure wouldnt it stop the refresh? the picture box is an animation so the animation would be true but would not run? – lemunk Nov 24 '11 at 15:45
  • Yes, no UI "action" is going to be performed as long as your data operation keeps the UI busy. If it's an animation you would have to process the data operation on a separate thread instead. – C.Evenhuis Nov 24 '11 at 15:48
  • ok, so. i create a new class called MyThread in that class i instansiate the form1 class and use the picture animation set visible too true. then in my form class i call instantiate the thread and.....wait.... brains is to frazzled, how does OO work with threads? – lemunk Nov 24 '11 at 15:54
  • I think OO and threading live in separate worlds, but your idea to put the data downloading code in a separate class sounds good (I'd name it something like BackgroundDownloader). Have a look at http://msdn.microsoft.com/en-us/library/ms951089.aspx for some examples and background information. – C.Evenhuis Nov 24 '11 at 16:00
  • yeh thinking about OO and threads has cause me to have my 6th ciggerette in the last 2hrs! but yeh putting my data download into my thread called myclass. then leaves my form with direct access to the controls. but im wandering what would be the condition to stop the thread due to download data can have a different run time each time the user decideds to download so i need to be a bit dynamic and not hard code a "sleep"(i think thats the right call, not too sure). – lemunk Nov 24 '11 at 16:08
  • The thread should just "run out of code" whenever it has finished downloading. You may have noticed the overloads on creating a new thread, you can supply an object there - you could consider passing an object that the thread updates to indicate it's done processing. In the download thread you could also call the `MyForm.Invoke(MyGuiNotificationMethod)` method to notify the form, as the GUI thread is no longer being occupied it will be able to clean up, hide the animation and show the results. – C.Evenhuis Nov 25 '11 at 07:35
0

Have you tried with Tasks???

I make a simple test to show how I would make something similar (in WPF):

The XAML:

<Window x:Class="TaskLoading.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="90,33,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
        <Image Height="118" HorizontalAlignment="Left" Margin="90,80,0,0" Name="imgLoading" Stretch="Fill" VerticalAlignment="Top" Width="122" Visibility="Hidden"  Source="/TaskLoading;component/loader_big.gif" />
    </Grid>
</Window>

The code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Threading.Tasks;

namespace TaskLoading
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void bigProcess(){
            Thread.Sleep(5000);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            imgLoading.Visibility = Visibility.Visible; //Make the icon visible.

            /* Start the bigProcess in a background thread: */
            Task changeIcon = Task.Factory.StartNew(() => 
                {
                    bigProcess();
                });

            /* At the end of the process make invisible the icon */
            changeIcon.ContinueWith((r) =>
            {
                imgLoading.Visibility = Visibility.Hidden;

            },
                TaskScheduler.FromCurrentSynchronizationContext()
            );
        }
    }
}
Galled
  • 3,958
  • 2
  • 25
  • 41
  • hmm looks interesting ill give it a try, what is a task btw? does it act like a synced function or thread? – lemunk Nov 24 '11 at 15:47
  • ah, error needs an arg in the currentsynch method, Error 1 Delegate 'System.Action' does not take 0 arguments – lemunk Nov 24 '11 at 15:49
  • @StevenSmith I update my answer. The task create a thread that cannot modify the UI, but with `TaskScheduler.FromCurrentSynchronizationContext()` you can modify the UI. – Galled Nov 24 '11 at 15:58
  • thanks for the update, it has removed the error. but its does not show the picture box giff animation. ive never seen tasks before, could you reflect on why this would happen? – lemunk Nov 24 '11 at 16:04
  • @StevenSmith I updated my answer to show an example, the bad part is that you need to show first your icon, then fill your DataSet in the method `bigProcess()` and at the end of your process hide your icon. – Galled Nov 25 '11 at 20:10
-2
    public void animateIcon()
    {
        Action action=()=>pictureBox2.Visible = true; 
        // animate
        this.Invoke(action);
    }
Reza ArabQaeni
  • 4,725
  • 24
  • 43