2

Hopefully this isn't too difficult to follow.

I'm currently working on a small timelogging application that runs quietly in the background. Every time the ticker runs down, the application prompts the user to say what he/she was doing since the last prompt. I'll eventually have the application write the data into a spreadsheet.

One of the options I have so far enables the user to choose whether or not he/she would like to use the default prompting setting (every time a prompt is missed, it stays visible until the next one is created, meaning if the user leaves his/her computer for a while there may be a fair few prompts sitting on the screen waiting to be filled in) or would like to combine all the prompts (every time a prompt is missed and a new one pops up, the old one is closed and the new one covers the time of the old prompt and the new prompt).

The user can also select a tickbox to turn prompts off. When he/she turns prompts back on again, a prompt pops up asking the user to fill in what he/she was doing while prompts were turned off (useful when the user is running fullscreen applications, etc).

My problem is, when I try to generate the prompts, they don't display correctly. I can't manipulate them at all and none of the controls display. They basically look like empty forms.

Here's my code for generating prompts using the ticker:

public void ticker(object source, System.Timers.ElapsedEventArgs e)
    {
        if (groupMissed)
        {
            incrementsMissed += 1;
            if (incrementsMissed > 1)
            {
                IncrementForm form = (IncrementForm)Application.OpenForms["IncrementForm"];
                if (form.InvokeRequired)
                {
                    form.Invoke(new MethodInvoker(delegate { form.Close(); }));
                }
            }
        }
        else
        {
            incrementsMissed = 1;
        }

        IncrementForm theIncrementForm = new IncrementForm(this, e.SignalTime);
        theIncrementForm.Show();
        latestIncrement = e.SignalTime;
    }

And here's my code for generating prompts using the "turn prompts off" checkbox:

private void chkbxAlerts_Click(object sender, EventArgs e)
    {
        if (!chkbxAlerts.Checked)
        {
            // Ensures that the time missed is covered and restarts the timer
            DateTime now;
            now = DateTime.Now;
            if ((now - latestIncrement).TotalMinutes >= 1) // Only records time if it is equal to or greater than one minute
            {
                // TO-DO: FIX
                if (groupMissed)
                {
                    incrementsMissed += 1;
                    if (incrementsMissed > 1)
                    {
                        IncrementForm form = (IncrementForm)Application.OpenForms["IncrementForm"];
                        if (form.InvokeRequired)
                        {
                            form.Invoke(new MethodInvoker(delegate { form.Close(); }));
                        }
                    }
                }
                else
                {
                    incrementsMissed = 1;
                }
                IncrementForm theIncrementForm = new IncrementForm(this, now, latestIncrement);
                theIncrementForm.Show();
                latestIncrement = now;
            }
            timer.Enabled = true;
        }
        else
        {
            // Stops the timer
            timer.Enabled = false;
        }
    }

If you need any further clarification, please let me know. Thanks so much in advance for any help, this has been bugging me.

Djentleman
  • 1,137
  • 5
  • 13
  • 28
  • a very well formed question - though still some things missing. Is your ticker a timer-driven? timer is calling from another thread - You cannot update GUI form from in there (though you can show / spawn forms in different threads - not sure what might end up 'giving' in your case)...yes, that's another thread sure – NSGaga-mostly-inactive Apr 19 '12 at 00:55
  • Yes, the ticker is called by the Elapsed event on a timer. How can I get around this GUI problem you mention? – Djentleman Apr 19 '12 at 00:57
  • could you craft a small 'runnable' example? there's more to this I'm guessing – NSGaga-mostly-inactive Apr 19 '12 at 01:06
  • @NSGaga I just did a quick bit of research and realised that the problem is indeed because I am trying to perform UI functions within the timer thread, like you said. I just need to figure out how to thread said functions through the UI thread. This is confusing stuff. – Djentleman Apr 19 '12 at 01:12
  • do you have a main window running at all times? – NSGaga-mostly-inactive Apr 19 '12 at 01:15
  • That's correct, it runs in the taskbar. It contains my timer code and all the code you see above. – Djentleman Apr 19 '12 at 01:16

2 Answers2

2

I think, from what I can see, not 100% but your timer is spawning your windows in a separate thread (being from the timer ticker call).

While theoretically that can work (take a look at this How to open a form in a thread and force it to stay open)
...you may be much better off staying within the main thread.

Try something like this...

yourMainWindow.Invoke(new MethodInvoker(() =>
    {
        IncrementForm theIncrementForm = new IncrementForm(this, e.SignalTime);
        theIncrementForm.Show();
        latestIncrement = e.SignalTime;
    }));

...that's from your timer - that way (as I can see) you should have it all 'on the main thread' and make things much easier for you.

hope this helps

Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • Thanks very much for your help! Even though I didn't end up using your approach, you really helped me understand threading more. So thanks again! – Djentleman Apr 19 '12 at 02:27
2

System.Timers.Timer has a SynchronizingObject property. If you set that to the main form (or the form that contains the timer), then the timer tick event will be raised on the GUI thread.

Do note that System.Timers.Timer has the nasty habit of swallowing exceptions that occur in the Elapsed event. If your tick handler throws an exception, you'll never see it. It's a nasty bug hider. For that reason, I recommend using either System.Windows.Forms.Timer or System.Threading.Timer. If you use the Windows Forms timer, the elapsed event is raised on the GUI thread. If you use System.Threading.Timer, you'll have to use Invoke as NSGaga shows in his answer.

See Swallowing exceptions is hiding bugs for more information about why I discourage the use of System.Timers.Timer.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Your solution works well, thanks! Just one thing. System.Windows.Forms.Timer doesn't seem to have an equivalent to System.Timers.ElapsedEventArgs.SignalTime so I just used DateTime.Now instead. Thought you might want to know if you didn't already. – Djentleman Apr 19 '12 at 02:26