tags:

views:

78

answers:

6

I'm writing an application which requires a status bar update for while a database is being loaded. I set the label to "Database loading..." then load the database with a BackgroundWorker. When the worker completes, I set the label to "Database loaded." This is only done on startup, however, and I don't want the label to be visible for much longer, and would like it cleared several seconds after the worker completes. I could put a dedicated timer object on my main for, but this single action would be its only job, and it seems like a more elegant solution should exist. So I tried lambdas:

void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    DataLabel.Text = "Database Loaded";
    System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
    timer.Interval = 5000;
    timer.Tick += new EventHandler((o, a) =>
    {
        DataLabel.Text = "";
        (o as System.Windows.Forms.Timer).Enabled = false;
    });
}

Of course timer's scope expires after the function exits, and it's Tick event is never called.

How can I get a simple, single-fire event to run without the use of global-instance timers?

A: 

Usually, even in Visual studio, the status persists until user do another action. And that seems to be a better approach, as it may be possible that user wont be able to check the status in 5 or 10 second. I think its better to update the status when use do some another action (like clicking menu etc)

cornerback84
+1  A: 

You can create a class variable Timer, use it once and then null it.

Also, you could register a method with the ThreadPool - in this thread, sleep for the desired amount before triggering a call to the UI to update the label.

Or you could re-use the background worker for the same effect. This saves you needing to Control.Invoke onto the UI thread...

Adam
+2  A: 

Maybe you can use async invocation of some method with Thread.Sleep(N) in its body.

Stremlenye
+1 my thoughts exactly lol
Adam
A: 

You could probably just make the background thread wait a bit longer, e.g.:

void dataLoader_DoWork(object sender, DoWorkEventArgs e) {
    // Do work normally.

    // Report progress as complete.
    var worker = sender as BackgroundWorker;
    worker.ReportProgress(100);

    Thread.Sleep(5000);
}

void dataLoader_ProgressChanged(object sender, ProgressChangedEventArgs e) {
  if (e.ProgressPercentage == 100) {
    // Set label here
    DataLabel.Text = "Database Loaded";
  }
}

void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    DataLabel.Text = "";
}

This might work a little better than firing of an async method, as a thread has already been created for the background worker anyways. Would that work better?

Matthew Abbott
I'm fairly certain the BackgroundWorker uses the ThreadPool anyway.
Adam
+1  A: 

Your code is mostly fine. In fact, your general architecture is the most elegant solution in my opinion. You just forgot to start the timer. You do not need to worry about the GC prematurely collecting the timer because it will actually "root" itself automatically when it is started. That of course begs the question of whether or not this would cause a memory leak since a new timer is created everytime. I think not since the timer will also "unroot" itself when it is stopped. So the following should work fine.

void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)  
{  
    DataLabel.Text = "Database Loaded";  
    var timer = new System.Windows.Forms.Timer();  
    timer.Interval = 5000;  
    timer.Tick += (o, a) =>  
    {  
        timer.Stop();
        DataLabel.Text = "";
    };
    timer.Start();
}  
Brian Gideon
I can't believe I forgot to start the timer... I was so eager to blame the GC! Unfortunately, it doesn't appear GC gets it when it's stopped, so I just switched `Stop` for `Dispose`. (I hooked a handler to its `Disposed` event and it was never called.) Also, you have a stray parenthesis in the line `});`
Daniel Rasmussen
@Daniel: I fixed the typo. Regarding the GC...I said that the timer roots itself. I looked at it in the Reflector and the way it is doing it is by calling `GCHandle.Alloc` when the timer starts and then it frees the handle when it is stopped. Calling `Dispose` or `Stop` does the same thing from what I can tell. So stopping the timer should be sufficient. I think the `Disposed` event is raised only when `Dispose` is called and not when the GC runs the finalizer.
Brian Gideon
A: 

Queue a WorkItem in the ThreadPool, Sleep for 5 seconds, and then BeginInvoke the update behavior. Here's a basic example:

using System;
using System.Threading;
using System.Windows.Forms;

namespace CSharpScratch
{
    class Program
    {
        private static void Main()
        {
            var myForm = new MyForm();
            myForm.ShowDialog();
        }
    }

    class MyForm : Form
    {
        private readonly Label _label;

        public MyForm()
        {
            _label = new Label {Text = "Hello", Parent = this};
            Load += FormLoaded;
        }

        public void FormLoaded(object sender, EventArgs args)
        {
            ThreadPool.QueueUserWorkItem(x =>
                {
                    Thread.Sleep(5000);
                    BeginInvoke(new Action(() => _label.Text = "Goodbye"));
                });
        }
    }
}
statenjason