Link to home
Start Free TrialLog in
Avatar of rwheeler23
rwheeler23Flag for United States of America

asked on

How to add a timer behind call to stored precudure using C#?

I have a lengthy stored procedure that is called from my C# program. I have placed a label on the form and want that label to click up one second as long as the stored procedure runs. When it is done, I want to the label to disappear. How do I get this label to behave like a timer? Do I need to use backgrounder worker to accomplish this? Any sample code would be much appreciated.
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Given that you said "form", I'm assuming it's Winforms. There are a few ways to do this, and if you needed a high degree of precision (sub-second), you'd probably use the Stopwatch class, but for basic things like this, you can just use a timer that does some DateTime difference and formats the output.

Winforms:
1. Add a Timer object (best to drag it in via the designer - never add a Timer via code unless you understand how Timers are specifically different from other controls). Name it tmrProcedureRuntime.

2. Add a DateTime property to the form class, name it dtProcedureStarted.

3. During form load, set the timer interval to 1000 and add a Tick event handler.

4. Immediately before you start your procedure, set dtProcedureStarted to DateTime.Now, start tmrProcedureRuntime, and make the label visible (you didn't specify the name, so I'll call it lblProcedureRuntime).

5. In the timer Tick handler, use this code:
private void tmrProcedureRuntime_Tick(object sender, EventArgs e)
{
    lblProcedureRuntime.Text = DateTime.Now.Subtract(dtProcedureStarted).ToString(@"hh\:mm\:ss");
    lblProcedureRuntime.Invalidate();
    lblProcedureRuntime.Refresh();    
}

Open in new window


6. After the procedure ends, make the label invisible again and stop the timer.

On a side note, you should ideally use async/await or a BackgroundWorker to kick off the stored procedure so that the main UI thread isn't held up waiting for the SP to end.

For WPF, it would be a similar process, but you would normally place a string property into your viewmodel and have the timer call a method in that viewmodel to do the work of calculating the desired value and set the string property to it. Then you would use binding to bind the Text/Content property of your label to that string property in your viewmodel.
Avatar of rwheeler23

ASKER

I like your idea of using async/await and/or backgroundworker. I am going to test a few scenarios. If you have any tips or links please forward them along. Please excuse my ignorance, how do I tell if this is WPF or Winforms? I simply start off the project by selecting a project type off a toolkit I was given.
If it's WPF, then you will be designing your layout by writing (usually) XAML. If you don't have a place where you're writing XAML like:
<Grid>
  <TextBox ... />
  <Button ... />
</Grid>

...then you're probably on Windows Forms. You can also look at the project properties.
My code is below. I am getting this message about 'e' variable. What must I change?
===================================================================
Severity      Code      Description      Project      File      Line      Suppression State
Error      CS0136      A local or parameter named 'e' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter      GSE.GP.Customizations      C:\Projects\GSE.GP.Customizations\GSE.GP.Customizations\frmJobLinkerUtilities.cs      134      Active

===========================================================
        private void btnJobCostDetail_Click(object sender, EventArgs e)
        {
            Class1 c = new Class1();
            c.StartClock(ref lblTimer);

            var backgroundWorker = new BackgroundWorker();

            /* Perform mass refresh of job cost detail */
            SqlParameter[] sqlParameters = new SqlParameter[0];

            backgroundWorker.DoWork += (s, e) =>
            {
                try
                {
                    string commandText = "rbsBuildJobCostDetail";

                    DataAccess.ExecuteNonQuery(Controller.Instance.Model.GPCompanyDatabase, CommandType.StoredProcedure, commandText, sqlParameters); /* Perform mass refresh of contracts(jobs) */

                    MessageBox.Show("Job Cost Detail has been refreshed");
                }
                catch (Exception ex)
                {
                    if (Model.stackTraceWanted)
                    {
                        string eMsg = "GPADDINS ERROR : An unexpected error occurred in refreshing job cost detail " + ex.Message;
                        eMsg += "\n" + ex.StackTrace;
                        MessageBox.Show(eMsg);
                    }

                    MessageBox.Show("An unexpected error occured in refreshing job cost detail: " + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            };

            backgroundWorker.RunWorkerCompleted += (s, e) =>
            {
                c.StopClock();
            };

            backgroundWorker.RunWorkerAsync();
        }
ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
What am I missing here? I took a shot at modifying to not use inline methods. My first method correctly displays a count. The modified second version does not display the count.
BGW-DoesWork.txt
BGW-DoesNotWork.txt
In your second version, you're using the "RunWorkerCompleted" twice. I think you probably meant to do:
backgroundWorker.DoWork += workCompletedJobCostDetail;
Here is my final solution. Why do you prefer to not use inline methods? I had found this code on the web and was completely unfamiliar with the structure.
BGW-DoesWorkNow.txt
I don't like inline methods (lambdas) for event handlers because you can't explicitly unsubscribe them and they mix method definitions inside other methods, which usually means messy code.

When you use inline methods, the compiler creates a singleton static class during compiling and then swaps out the inline methods for those generated class methods. So you're using a shortcut, but without a way to unsubscribe, you have a higher chance of running into a memory leak that could be problematic.
Thank you so much for your solution and valuable insight!