0

I am having an issue with my program. my program will let the user select a dropdown value and after they select the dropdown value the datagridview should load with the data from a table, and then the progress bar should start the percentage 1 to 100%. Now everything in my program works the datagridview loads correctly and everything else in the program. the progress bar also works and loads but the issue comes when the user selects the dropdow combobox the progress bar takes like 15 to 20 seconds to start. I would like it to right away.

Can you see my code and see what could be the issue to why the progress bar is not starting right away?

if you need more information please let me know.

namespace DatagridViewProgressBar
{
    public partial class Form1 : Form
    {

        //datagridview, bindingsource, data_apapter global objects variables
        private DataGridView dataGridView = new DataGridView();
        private BindingSource bindingSource = new BindingSource();
        private SqlDataAdapter dataAdapter = new SqlDataAdapter();
        DataTable dt = new DataTable();


        //class objects
        Databases lemars = new Databases();
        Databases schuyler = new Databases();
        Databases detroitlakeskc = new Databases();


        public Form1()
        {
            InitializeComponent();
            // To report progress from the background worker we set this property
            dbWorker = new BackgroundWorker();
            dbWorker.DoWork += new DoWorkEventHandler(dbWorker_DoWork);
            dbWorker.ProgressChanged += new ProgressChangedEventHandler(dbWorker_ProgressChanged);
            dbWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(dbWorker_RunWorkerCompleted);
            dbWorker.WorkerReportsProgress = true;
            dbWorker.WorkerSupportsCancellation = true;

        }

        private void btn_Exit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void comboBox_Database_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (comboBox_Database.SelectedItem.ToString() == "LeMars21St")
            {

                if (dbWorker.IsBusy != true)
                {
                    dbWorker.RunWorkerAsync();
                }            
            }
        }



        private void GetTableToDataGridView()
        {
            //prgBar_DataGridViewLoading
            DatabaseColumns Obj = new DatabaseColumns();
            String SqlcmdString = @"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
            SqlDataReader reader;
            int progress;

            using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
            {
                reader = null;
                SqlCommand Sqlcmd = new SqlCommand(SqlcmdString, conn);
                conn.Open();
                reader = Sqlcmd.ExecuteReader();
                if (reader.HasRows)
                {
                    try
                    {

                        dt.Load(reader);

                        for (int i = 0; i < dt.Rows.Count; i++)
                        {
                            Obj.Invoice = dt.Rows[i]["invoice"].ToString();
                            Obj.Shipment = dt.Rows[i]["shipment"].ToString();
                            Obj.Project = dt.Rows[i]["Project"].ToString();
                            Obj.InvoiceDateTB = Convert.ToDateTime(dt.Rows[i]["invoiceDateTB"]);
                            Obj.CreatedDate = Convert.ToDateTime(dt.Rows[i]["CreatedDate"]);
                            Obj.TypeName = dt.Rows[i]["typeName"].ToString();
                            Obj.ExportedDate = Convert.ToDateTime(dt.Rows[i]["exportedDate"]);
                            Obj.StatusName = dt.Rows[i]["statusName"].ToString();
                            Obj.Total = Convert.ToDecimal(dt.Rows[i]["total"]);
                            Obj.ImportStatus = dt.Rows[i]["import_status"].ToString();
                            if (!Convert.IsDBNull(dt.Rows[i]["Time_Completed"]))
                            {
                                Obj.TimeCompleted = Convert.ToDateTime(dt.Rows[i]["Time_Completed"]);
                            }
                            Obj.ErrorDescription = dt.Rows[i]["ERROR_DESCRIPTION"].ToString();

                            progress = i * 100 / dt.Rows.Count; 
                            dbWorker.ReportProgress(progress);
                            Thread.Sleep(500);
                        }                     
                    }
                    finally
                    {
                        conn.Close();
                    }
                }
            }
        }

        private void dbWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            GetTableToDataGridView();
            dbWorker.ReportProgress(100);
        }

        private void dbWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar_GetTasks.Value = e.ProgressPercentage;
            // eg: Set your label text to the current value of the progress bar
            lbl_PercentageCount.Text = (progressBar_GetTasks.Value.ToString() + "%");         
        }

        private void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

            dataGridView_ShowAllData.DataSource = dt;

            if (e.Cancelled)
            {
                 MessageBox.Show("Process Cancelled.");
            }
            else if (e.Error != null)
            {
                MessageBox.Show("Error occurred: " + e.Error.Message);
            }
            else
            {
                MessageBox.Show("Successful Completion.");
            }

            //progressBar_GetTasks.Value = 0;
        }

        private void btn_CancelOperation_Click(object sender, EventArgs e)
        {
            if (dbWorker.IsBusy)
            {
                dbWorker.CancelAsync();
            }
        }
    }
}
FunCoder
  • 147
  • 1
  • 2
  • 16
  • How many rows are you loading here? – Steve Sep 22 '17 at 21:29
  • that varies, sometimes will load 50 records or 2 records each day will show different number of records. but I tried with many records a less records always the progress bar takes 15 to 20 seconds to start the bar loading. after the 15 seconds the progress bar loads correctly 1 to 100%. I just would like it to start the progress bar right away when the user selects the combobox values. – FunCoder Sep 22 '17 at 21:32
  • fyi, you don't need to call `conn.Close()`, the using statement does it for you. – Scott Chamberlain Sep 22 '17 at 21:35
  • `the progress bar takes like 15 to 20 seconds to start.` That is likely due to the amount of time taken to execute the SQL (and your use of `dt.Load` which other answers have discussed). Put a breakpoint on `dbWorker.ReportProgress(progress);` - how long does it take to get to that line? If it takes (for example) 15 seconds you have two broad options. a) Speed up the code before that line of code. b) Move the `ReportProgress` call earlier in your code (before the slow bit). – mjwills Sep 22 '17 at 21:51
  • Also consider changing `progress = i * 100 / dt.Rows.Count;` to `progress = Convert.ToInt32(Math.Ceiling((double)i * 100 / dt.Rows.Count));`. By using double rather than int division, and using Math.Ceiling, the progress bar will begin to move **slightly** earlier. – mjwills Sep 22 '17 at 21:54
  • that helped @mjwills, thanks man. I changed the sql query table in the database because the table took, 15 to 20 seconds to load everytime, now the table takes 1 to 2 seconds to load, and now in the c# application the progress bar runs quick, after making selection in the comobobox. – FunCoder Sep 22 '17 at 23:41

2 Answers2

1

Doing dt.Load(reader); waits for the data to fully load before continuing, Get rid of that line and replace it with a while(reader.Read()) loop.

    private void GetTableToDataGridView()
    {
        //prgBar_DataGridViewLoading
        DatabaseColumns Obj = new DatabaseColumns();
        String SqlcmdString = @"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
        String CountcmdString = @"SELECT count(*) FROM dbo.AllInvoicesInReadyStatus";
        SqlDataReader reader;
        int progress;
        int total;

        using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
        {
            reader = null;
            SqlCommand Sqlcmd = new SqlCommand(CountcmdString , conn);
            conn.Open();
            total = (int)Sqlcmd.ExecuteScalar(); //Get the total count.
            Sqlcmd.CommandText = SqlcmdString;
            using(reader = Sqlcmd.ExecuteReader()) //this should be in a using statement
            {
                while(reader.Read())
                {
                    object[] row = new object[reader.VisibleFieldCount];
                    reader.GetValues(row);
                    LoadSingleRowInToTable(dt, row); //I leave this to you to write.

                    //You can just read directly from the reader.
                    Obj.Invoice = reader["invoice"].ToString();
                    Obj.Shipment = reader["shipment"].ToString();
                    Obj.Project = reader["Project"].ToString();
                    Obj.InvoiceDateTB = Convert.ToDateTime(reader["invoiceDateTB"]);
                    Obj.CreatedDate = Convert.ToDateTime(reader["CreatedDate"]);
                    Obj.TypeName = reader["typeName"].ToString();
                    Obj.ExportedDate = Convert.ToDateTime(reader["exportedDate"]);
                    Obj.StatusName = reader["statusName"].ToString();
                    Obj.Total = Convert.ToDecimal(reader["total"]);
                    Obj.ImportStatus = reader["import_status"].ToString();
                    if (!Convert.IsDBNull(reader["Time_Completed"]))
                    {
                        Obj.TimeCompleted = Convert.ToDateTime(reader["Time_Completed"]);
                    }
                    Obj.ErrorDescription = reader["ERROR_DESCRIPTION"].ToString();

                    //Only call report progress when the progress value changes.
                    var newProgress = i * 100 / total;
                    if(progress != newProgress)
                    {
                        progress = newProgress;
                        dbWorker.ReportProgress(progress);
                    }
                    //Thread.Sleep(500); 
                }
            }
        }
    }

UPDATE: Here is a example based on Steve's deleted answer that shows a better solution without using a DataTable.

private void dbWorker_DoWork(object sender, DoWorkEventArgs e)
{
    List<DatabaseColumns> data = GetTableToList();
    if (data == null) //data will be null if we canceled.
    {
        e.Cancel = true;
    }
    else
    {
        e.Result = data;
    }
    dbWorker.ReportProgress(100);
}
private void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{

    if (e.Cancelled)
    {
        MessageBox.Show("Process Cancelled.");
    }
    else if (e.Error != null)
    {
        MessageBox.Show("Error occurred: " + e.Error.Message);
    }
    else
    {
        dataGridView_ShowAllData.DataSource = e.Result; //use the result from thebackground worker, only use if not canceled or errored.
        MessageBox.Show("Successful Completion.");
    }

    //progressBar_GetTasks.Value = 0;
}
private List<DatabaseColumns> GetTableToList()
{
    List<DatabaseColumns> data = new List<DatabaseColumns>();
    //prgBar_DataGridViewLoading
    String SqlcmdString = @"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
    String CountcmdString = @"SELECT count(*) FROM dbo.AllInvoicesInReadyStatus";

    using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
    {
        SqlCommand Sqlcmd = new SqlCommand(CountcmdString, conn);
        conn.Open();
        var total = (int)Sqlcmd.ExecuteScalar();
        Sqlcmd.CommandText = SqlcmdString;
        int i = 0;
        int progress = 0;
        using (SqlDataReader reader = Sqlcmd.ExecuteReader()) //this should be in a using statement
        {
            while (reader.Read())
            {
                if (dbWorker.CancellationPending)
                {
                    //Exit early if operation was canceled.
                    return null;
                }
                DatabaseColumns Obj = new DatabaseColumns();

                //You can just read directly from the reader.
                Obj.Invoice = reader["invoice"].ToString();
                Obj.Shipment = reader["shipment"].ToString();
                Obj.Project = reader["Project"].ToString();
                Obj.InvoiceDateTB = Convert.ToDateTime(reader["invoiceDateTB"]);
                Obj.CreatedDate = Convert.ToDateTime(reader["CreatedDate"]);
                Obj.TypeName = reader["typeName"].ToString();
                Obj.ExportedDate = Convert.ToDateTime(reader["exportedDate"]);
                Obj.StatusName = reader["statusName"].ToString();
                Obj.Total = Convert.ToDecimal(reader["total"]);
                Obj.ImportStatus = reader["import_status"].ToString();
                if (!Convert.IsDBNull(reader["Time_Completed"]))
                {
                    Obj.TimeCompleted = Convert.ToDateTime(reader["Time_Completed"]);
                }
                Obj.ErrorDescription = reader["ERROR_DESCRIPTION"].ToString();

                //Add the object to the list.
                data.Add(Obj);

                //Only call report progress when the progress value changes.
                var newProgress = i * 100 / total;
                if (progress != newProgress)
                {
                    progress = newProgress;
                    dbWorker.ReportProgress(progress);
                }
                i++;
            }
        }
    }
    return data;
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • You were correct in your comment on my answer. Probably this should be done with a Marquee Progress bar to avoid a double call to the db. – Steve Sep 22 '17 at 21:55
  • what do you mean in the LoadSingleRowInToTable(dt, row); what will that function do?, can you explain that. – FunCoder Sep 22 '17 at 22:10
  • @AndresBryan29 well it takes a `Object[]` and adds it to `dt` as a row using the same rules as `dt.Load()` would use (merging duplicates together using the primary key). I did not want to take the work to write that all out. However, honestly I would not use a `DataTable` at all and instead use a `List` to represent the data and add the rows to that. If you do make that change make sure to bring ` DatabaseColumns Obj = new DatabaseColumns();` inside the while loop or else you will only get one repeated row in the list. – Scott Chamberlain Sep 22 '17 at 22:45
  • @ScottChamberlain, if not asking much, can you show some short code example of the following: reader.GetValues(row); LoadSingleRowInToTable(dt, row); and the List, and after I should be able to make the correct adjustments. – FunCoder Sep 22 '17 at 23:55
  • @ScottChamberlain, I need some steps of what are you referring to the two functions GetValues(row) and the LoadSingleRowInTable(dt, row), I did add the List to my code. if you can give some example of the functions I mention would be great – FunCoder Sep 23 '17 at 18:13
  • GetValues is a function that is part of DataReader, it gets one row and fills a `object[]` the other function was a fake name I made up that would insert those `object[]` in to your data table. However I would reccomend not using a DataTable at all and instead use a List to hold the data. Steve had a good example but he deleted his question, I will copy his deleted answer and tweak it to match mine. – Scott Chamberlain Sep 23 '17 at 18:36
  • That actually worked, thanks to all for the work on this. just one last thing I would like to add the total numbers of rows assign it to a label.text, how could I archived that I tried adding the total executeScalar variable to the label but I get an error can you guys show me that last thing, thanks. – FunCoder Sep 23 '17 at 19:39
  • Google your error, i know what error you are getting and you will get 100's of results of duplicate questions asking about the same error. – Scott Chamberlain Sep 23 '17 at 19:45
0

Something I had when doing my first backgroundWorker, was the GUI locking up. I made the mistake of trying to hand out every result (primnumbers in a range from 2 to selected bound) as it was found via report progress. If I had a long enough operation (it did not happen if the operation was short) I ended up overloading and locking up the GUI with write operations, making it appear I never actually added multithreading.

Now I noticed this line:

progress = i * 100 / dt.Rows.Count; 
You run the progress code (and update the bar) every loop itteration, even if the percentage did not actually change. If you process 1 million itterations, that is 10000 redraws without the value actually changing. This might slow down later as hits become rarer or the GC starts interferring with optimal performance of the task so the GUI thread has "time to catch up".

You should check if the value actually changed, before writing it to the GUI. Latest in the ProgressReporting Event. But it might be possible to do something back in the loop itself.

I made some example code to showcase what I call the "GUI write overhead" problem:

using System;
using System.Windows.Forms;

namespace UIWriteOverhead
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        int[] getNumbers(int upperLimit)
        {
            int[] ReturnValue = new int[upperLimit];

            for (int i = 0; i < ReturnValue.Length; i++)
                ReturnValue[i] = i;

            return ReturnValue;
        }

        void printWithBuffer(int[] Values)
        {
            textBox1.Text = "";
            string buffer = "";

            foreach (int Number in Values)
                buffer += Number.ToString() + Environment.NewLine;
            textBox1.Text = buffer;
        }

        void printDirectly(int[] Values){
            textBox1.Text = "";

            foreach (int Number in Values)
                textBox1.Text += Number.ToString() + Environment.NewLine;
        }

        private void btnPrintBuffer_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Generating Numbers");
            int[] temp = getNumbers(10000);
            MessageBox.Show("Printing with buffer");
            printWithBuffer(temp);
            MessageBox.Show("Printing done");
        }

        private void btnPrintDirect_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Generating Numbers");
            int[] temp = getNumbers(1000);
            MessageBox.Show("Printing directly");
            printDirectly(temp);
            MessageBox.Show("Printing done");
        }
    }
}
Christopher
  • 9,634
  • 2
  • 17
  • 31
  • 1
    It is possible that he is swamping his message queue, but that would not have the "start after a long pause" behavior, it is much more likely the call to `dt.Load(reader);` is taking 20 seconds to complete because the entire query must complete before it continues. – Scott Chamberlain Sep 22 '17 at 21:49
  • You are right, now that I think about it, the initialisation work taking 15-20 seconds seems a likely culptript. I may have misread this as "the progressbar is frozen for 15-20 because I accidently overpower the GUI thread". – Christopher Sep 22 '17 at 21:58