I made a Windows Forms application that calculates the factorial of a number. All is fine, but now I have to do it using events. The concept of events is new to me and I've been trying to make it work for the past three days to no avail.
I have the form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
... some function declarations...
//public event EventHandler ProgressBarChanged;
public int OnProgressBarChanged()
{
progressBar1.Value++;
return progressBar1.Value;
}
public void button2_Click(object sender, EventArgs e)
{
initialize();
label3.Visible = false;
int wait_time = telltime();
int number = reading();
Facto mth;
if (checkBox1.Checked && checkBox2.Checked)
{
mth = new Facto(label3, wait_time, progressBar1);
}
else if(checkBox1.Checked==false && checkBox2.Checked)
{
mth = new Facto(label3,wait_time);
}
else if (checkBox1.Checked && checkBox2.Checked == false)
{
checkBox1.Checked = false;
mth = new Facto();
}
else
{
mth = new Facto();
}
mth.Subs += new Eventhandler(OnProgressBarChanged); // Error. I don't understand why
int result = mth.Factorial(number);
string display = result.ToString();
label3.Visible = true;
label3.Text = display;
}
And the Facto class:
public class Facto
{
public event EventHandler Subs;
System.Windows.Forms.Label label_for_output;
int wait_time;
System.Windows.Forms.ProgressBar bar;
public Facto()
{
}
public Facto(System.Windows.Forms.Label l, int time)
{
label_for_output = l;
wait_time = time;
}
public int Factorial(int number_to_calculate)
{
int Result;
if (Subs != null)
{
Subs(this, new EventArgs());
}
System.Threading.Thread.Sleep(wait_time);
if (number_to_calculate == 0)
{
return 1;
}
else
{
Result = (number_to_calculate * Factorial(number_to_calculate - 1));
if (label_for_output != null)
{
label_for_output.Visible = true;
label_for_output.Text = Result.ToString();
label_for_output.Update();
}
else
Console.WriteLine(Result);
}
System.Threading.Thread.Sleep(wait_time);
return Result;
}
}
}
The event should be triggered when the recursive function calls itself. When the event is triggered progressbar1.value from Form1 should be incremented with 1 (it should also decrement when it comes back from recursion, but I'm more interested in getting it to work first).
How can I fix this?
It's really confusing to me and I can only find examples which show messages or are very badly explained.
You have this error because your method signature are not equal:
.NET assumes that you have
public int OnProgressBarChanged() as
public void OnProgressBarChanged(object o, EventArgs e);
This is a standart signature of all .net events. First parameter - is object, that raised the event. Second parameter is event data.
You can create your custom class, inherited from EventErgs to pass data to event handler
For this kind of task I would suggest using a BackgroundWorker with a ProgressChanged event handler, so that the calculations and UI updates are carried out on separate threads. See the MSDN docs for a similar example with Fibonacci number calculation and a progress bar.
Related
I am working on a cardgame conform the MVVM pattern. My model contains the players, hands and cards as well as the game with its rules.
There are 2 classes not playing nice here: the "card" class that has a "submitted" event: when a player clicks on an image of the card, among other things the submitted event fires. This triggers the UI to move the card from a hand to the center of the window.
Next I have a class "trick", that all players add a card to. When the trick is full, it fires the TrickFull event: this triggers the UI to show the cards in the trick and then clear the table.
During gameplay the TrickFull event fires nanoseconds after the last card was submitted. This means the table is cleared before the 4th card can be shown. I would like to be able to force the UI to process the cardsubmitted event before the Trickfull event.
I have tried to accomplish this by Thread.Sleep (which does not work), I have also tried to move the TrickFull event to the gameclass (meaning it gets triggered much later). This works, but it does seem very out of place. I have looked into locking the events (but that does not seem to be the way to go), directly taking to control of the Dispatcher, changing the priority, or maybe calling the events asynchonously and blocking the stuff somehow in the EndInvoke.
I would like to know what the best solution for this would be. My research suggests that maybe Events would not be the best pattern for this behaviour, but I am stumped. Can you bright people please advise me on how to fix this (probably architectural) flaw?
Code below, beware: Dutch classnames and stuff in there
Card (=Kaart)
public class Kaart : IComparable<Kaart>
{
public readonly Kleur Kleur;
public readonly Waarde Waarde;
public Kaart(Kleur kleur, Waarde waarde)
{
Kleur = kleur;
Waarde = waarde;
}
public event KaartGespeeld Opgegooid;
public delegate void KaartGespeeld(Kaart kaart);
public void Opgooien()
{
Opgegooid?.Invoke(this);
}
public int CompareTo(Kaart other)
{
var comparer = new KlaverjasComparer(null, null);
return comparer.Compare(this, other);
}
public Speler Speler { get; set; }
}
Trick (=Slag)
public class Slag
{
private readonly List<Kaart> _kaarten;
[Browsable(false)]
public IReadOnlyList<Kaart> Kaarten => _kaarten;
public Slag(Kleur troef)
{
_kaarten = new List<Kaart>(4);
Troef = troef;
}
public Speler Winnaar { get; private set; }
public int Punten => PuntenTeller.Punten(this);
public int Roem => PuntenTeller.Roem(this);
[Browsable(false)]
public Kleur Troef { get; }
public Kleur GevraagdeKleur { get; set; }
[Browsable(false)]
public bool Vol =>_kaarten.Count == 4;
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
VolleSlag?.Invoke(this);
}
public event SlagIsVol VolleSlag;
public delegate void SlagIsVol(Slag slag);
}
ViewModel:
public TafelViewModel(Boompje boompje)
{
Speler1 = boompje.Deelnemers[0];
Speler2 = boompje.Deelnemers[1];
Speler3 = boompje.Deelnemers[2];
Speler4 = boompje.Deelnemers[3];
Troef = boompje.Potje.Troef;
//boompje.SlagIsVol += Boompje_SlagIsVol;
// ToDo: als ik naar dit event kijk gaat het mis
boompje.Potje.Slag.VolleSlag += Boompje_SlagIsVol;
boompje.Potje.TroefGedraaid += delegate { Troef = boompje.Potje.Troef; };
foreach (Speler _deelnemer in boompje.Deelnemers)
{
foreach (Kaart _kaart in _deelnemer.Hand)
{
_kaart.Opgegooid += moveKaart;
}
_deelnemer.DoeIkHet += DeelnemerOnDoeIkHet;
}
_spelerKaart = new Dictionary<Speler, string>
{
{Speler1, "Kaart1"},
{Speler2, "Kaart2"},
{Speler3, "Kaart3"},
{Speler4, "Kaart4"}
};
_spelerRichting = Dictionary.SpelersRichting(boompje.Deelnemers);
WinnaarVisible = Visibility.Hidden;
}
private void Boompje_SlagIsVol(Slag slag)
{
WinnaarVisible = Visibility.Visible;
Richting = _spelerRichting[slag.Winnaar];
Application.DoEvents();
Thread.Sleep(2000);
Kaart1 = null;
Kaart2 = null;
Kaart3 = null;
Kaart4 = null;
WinnaarVisible = Visibility.Hidden;
}
private void moveKaart(Kaart kaart)
{
PropertyInfo prop = GetType().GetProperty(_spelerKaart[kaart.Speler]);
prop?.SetValue(this, kaart);
}
public void OpKaartGeklikt(Kaart kaart)
{
if (kaart.Speler != Speler3)
{
return;
}
Speler3.SpeelKaart(kaart);
}
}
}
Set ManualResetEvent in your ViewModel
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Pass this object into Kaart of yours
ManualResetEvent _manualResetEvent;
public Kaart(Kleur kleur, Waarde waarde, ManualResetEvent manualResetEvent)
{
Kleur = kleur;
Waarde = waarde;
_manualResetEvent = manualResetEvent;
}
This is the method that's being invoked when card is added I assume
public void Opgooien()
{
Opgegooid?.Invoke(this);
_manualResetEvent.Set();
}
And the main part (you also need to pass ManualResetEvent to the Slag object.
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
var result = _manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
if(!result)
{
/* Did not receive signal in 5 seconds */
}
VolleSlag?.Invoke(this);
_manualResetEvent.Reset();
}
Just the basic concept, it might not work right away due to your code language and lack of some part of it in the example, but you should catch the idea
this is basically a follow up to a previous question (Triggering an event in c# from c++ and declaring LPCWSTR). I've revised my code based on the answers and comments I have received and I solved the initial issue, which was passing the event to the GpioSetupInterruptPin from a gpio api. I don't have a lot of documentation on the api but what i'm trying to achieve is: have a form with a white label; after pressing a switch, the label turns yellow.
The problem i'm having now is the event seems to trigger as soon as it's created (the "execute" message is passed to the debug dialog and the label turns yellow) but it doesn't do anything when i toggle the switch. I was told in the last question to use WaitForSingleObject but i'm not really sure where to call it and this article only added to my confusion.
public partial class Form1 : Form
{
// P/Invoke CreateEvent and WaitForSingleObject
private void GPIO_Open() //get handle for gpio
private void GPIO_Output() //output pin declaration
private void button1_Click(object sender, EventArgs e)
{
Interrupt_Setup();
}
private void Interrupt_Setup()
{
hGPIO = GPIOapi.GpioOpenHandle(); //returns a handle to the gpio
GIPO_ON = true;
Debug.WriteLine("Driver open \n" + hGPIO);
GPIO_Output(); //set output pins
GPIO_Interrupt(Trigger); //configure interrupt
}
private void GPIO_Interrupt(string trigger)
{
bool ok;
_Main();
//INTERRUPT DECALRATION
ok = GPIOapi.GpioSetupInterruptPin(hGPIO, port6, 4, GPIOapi.INT_TRIGGER_MODE.TRIGGER_MODE_EDGE,
GPIOapi.INT_TRIGGER_POLARITY.TRIGGER_POL_HIGH_RISING, trigger, true);
Thread waitThread=new Thread(WaitForTrigger);
waitThread.Start();
if (!ok)
Debug.WriteLine("NO interrupt");
else
Debug.WriteLine("Interrupt set for:" + port6 + "04" + " at " + hGPIO);
}
public static string Trigger = "InputProcessUpdateHandler";
public static IntPtr handle = CreateEvent(IntPtr.Zero, false, false, Trigger); //used P/Invoke
private static InputProcessor inputProcessor = null;
public Color[] color =
{
Color.Orchid, Color.DarkOrchid, Color.GreenYellow, Color.CornflowerBlue, Color.SteelBlue,Color.Crimson
};
public int i = 0;
public void WaitForTrigger()
{
while(true)
{try
{
if (WaitForSingleObject(handle, 0xFFFFFFFF) == false)
{
BeginInvoke(((System.Action)(() =>label2.BackColor = color[i])));
i++;
if (i > 4)
i = 0;
}
Thread.Sleep(300);
}
catch (Exception e)
{ Debug.WriteLine("exception: " + e); }}
}
}
private void _Main()
{
inputProcessor = new InputProcessor();
ShowToggle showToggle = new ShowToggle(inputProcessor);
inputProcessor.Process(label1);
}
public class ShowToggle
{
private InputProcessor _inputProcessor = null;
public ShowToggle(InputProcessor inputProcessor)
{
_inputProcessor = inputProcessor;
_inputProcessor.updateHandledBy += InputProcessUpdateHandler;
}
private void InputProcessUpdateHandler(Label label)
{
label.BackColor = Color.Yellow;
Debug.Write("execute");
}
}
public class InputProcessor
{
public delegate void InputProcessUpdateHandler(Label label);
public event InputProcessUpdateHandler updateHandledBy = null;
public void Process(Label label)
{
if (updateHandledBy != null)
updateHandledBy(label);
}
}
If anyone could help me with this, I would be very grateful.
*** I got it working but it looks a right mess. Could anyone help me straighten it out?
You code is really confusing to me. I think what you want is something like this. Bear in mind I'm typing this into the SO text editor, so don't expect it to compile and just work - it's a guide. Consider it a step above pseudocode.
public class DeviceInterrupt
{
IntPtr m_gpio;
string m_eventName;
public event EventHandler OnInterrupt;
public DeviceInterrupt(int port)
{
// get a driver handle
m_gpio = GPIO_Open();
// generate some unique event name
m_eventName = "GPIO_evt_" + port;
// wire up the interrupt
GpioSetupInterruptPin(m_gpio, port, m_eventName, ...);
// start a listener
new Thread(EventListenerProc)
{
IsBackground = true,
Name = "gpio listener"
}
.Start();
}
public void Dispose()
{
// TODO: release the handle
}
private void EventListenerProc()
{
// create the event with the name we sent to the driver
var wh = new WaitHandle(false, m_eventName);
while (true)
{
// wait for it to get set by the driver
if (wh.WaitOne(1000))
{
// we have an interrupt
OnInterrupt.Fire(this, EventArgs.Empty);
}
}
}
}
Usage would then be something like this:
var intr = new DeviceInterrupt(4);
intr.OnInterrupt += MyHandler;
....
void MyHandler(object sender, EventArgs a)
{
Debug.WriteLine("Interrupt occurred!");
}
Note
The Compact Framework doesn't support actual named system events, so the named WaitHandle I use in my code above is not a CF-supplied WaitHandle. Instead I'm using the one from the Smart Device Framework. You could also P/Invoke to CreateEvent and WaitForSingleObject yourself.
I have this code in my Form1:
public partial class Form1 : Form
{
public static int hours;
public static int minutes;
public static int seconds;
FinishGate finishgate = new FinishGate();
public Form1()
{
InitializeComponent();
txtHours.MaxLength = 2;
txtMinutes.MaxLength = 2;
txtSeconds.MaxLength = 2;
lblFinished.Text = Convert.ToString(gate.Total);
}
private void btnFinish_Click(object sender, EventArgs e)
{
hours = Convert.ToInt32(txtHours.Text);
minutes = Convert.ToInt32(txtMinutes.Text);
seconds = Convert.ToInt32(txtSeconds.Text);
lblFinished.Text = Convert.ToString(gate.Total);
// Check if a runner has been selected
if (lstRunners.SelectedIndex > -1)
{
// Obtain selected runner
Runner selectedRunner = (Runner)lstRunners.SelectedItem;
// If runner hasn't finished
if (selectedRunner.HasFinished == false)
{
// Call the method in FinishGate class to process the runner
FinishGate.ProcessRunner(selectedRunner);
}
else
{
// Runner has finished / been processed so increase the total that have completed the climb by one
finishgate.Total++;
}
}
}
}
Here is the FinishGate.cs:
class Gate
{
private int total;
public int Total
{
get { return total; }
set { total = value; }
}
public static void ProcessRunner(Runner selectedRunner)
{
}
}
What I want to happen is that when a runner in the listbox is selected and the Process button is clicked, the boolean hasFinished in ProcessRunner is changed to true and the Total integer is increased by one, which then updates the lblFinished to also increase by one, but I can't get it to work.
My two main issues are: I'm not sure what the code would be in ProcessRunner() to say if hasFinished == false change it to true, else leave the boolean the way it is. The other issue is getting the lblFinished to update accordingly when the integer increments.
Any advice over where I'm going wrong and how to prevent this in the future would be great.
You could try this:
Put this line
lblFinished.Text = Convert.ToString(gate.Total);
at the end of the btnFinish_Click() event instead of the beginning. This way it will update after ProcessRunner() is run.
Also, add this to your ProcessRunner() event in your Gate class:
if (selectedRunner.hasFinished == false)
{
selectedRunner.hasFinished = true;
}
//You don't need to do anything if it isn't false.
I hope this is of use to you. Let me know if I need to be more specific.
After reading online tutorials regarding events , I think I almost have an idea of whats going on. I developed the following extremely simple code to trigger an event in case a value is greater than 5.I know the code is pretty useless but I am using it to get my point across. (Instead of a main I just used a button event to trigger the code.)
//declare the delegate
public delegate void MyDelegate(string str);
public class SomeClass
{
public event MyDelegate MyEventFromDelegate;
private int i;
public int I
{
get
{ return i; }
set
{
if (value > 5)
{
MyEventFromDelegate("Value Greater than 5");
i = 0;
}
else
{
i = value;
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{ InitializeComponent(); }
public void Method_To_Call(String rx)
{ MessageBox.Show("This method will be called if greater than 5");}
private void button1_Click(object sender, EventArgs e)
{
SomeClass a = new SomeClass();
a.MyEventFromDelegate +=new MyDelegate(Method_To_Call);
a.I = 12;
}
}
The only concern I have here is when we want to raise an event with the statement
MyEventFromDelegate("Value Greater than 5");
What point is passing a parameters to the event is at this point if later (at button click event) we are actually going to assign it a function to call every time an event is triggered.
In your very simple example - there is no point, because SomeClass instance "a" is very short-lived, and because you are not using rx parameter passed to Method_To_Call.
Your form method button1_Click is connected to the button's Click event through a delegate. Button does not know what code will execute when it is clicked. All it has to do is to signal that is has been clicked. That signal is implemented using a delegate.
Your could have defined your delegate as having an integer parameter where the checked value is passed. Then although the event method would be invoked only when value is greater than 5, inside the event method you could do things differently depending on the actual value.
//declare the delegate
public delegate void MyDelegate(int aValue);
public class SomeClass
{
public event MyDelegate MyEventFromDelegate;
private int i;
public int I
{
get
{ return i; }
set
{
if (value > 5)
{
MyEventFromDelegate(value);
i = 0;
}
else
{
i = value;
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void Method_To_Call(int aValue)
{
MessageBox.Show("This method signals that value is greater than 5. Value=" + aValue.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
SomeClass a = new SomeClass();
a.MyEventFromDelegate +=new MyDelegate(Method_To_Call);
a.I = 12;
}
}
I have a WPF user control called TimerUserControl where contains a timer. And I have another user control where show questions, this one has a NextQuestion function.
The timer has 2 minutes like an interval, and I'd like to invoke the NextQuestion function when it has done. I think I have to use delegates, but I'm not sure.
UPDATE 1:
public partial class TimeUserControl : UserControl
{
public int _totalSeconds;
public int _secondsRemaining;
public DispatcherTimer timerSecondsLeft;
public TimeUserControl()
{
InitializeComponent();
timerSecondsLeft = new DispatcherTimer();
timerSecondsLeft.Tick += new EventHandler(timerSecondsLeft_Tick);
timerSecondsLeft.Interval = new TimeSpan(0, 0, 1);
}
public bool TimesUp
{
get;
set;
}
public void SetSeconds(int seconds)
{
timerSecondsLeft.Stop();
if (seconds == 0)
{
TimeTextBlock.Text = "There's no time! Hurray";
}
else
{
_totalSeconds = seconds;
_secondsRemaining = seconds;
TimeTextBlock.Text = string.Format("It remains {0} seconds. Don't take long!", _totalSeconds);
timerSecondsLeft.Start();
}
}
public void timerSecondsLeft_Tick(object sender, EventArgs e)
{
_secondsRemaining--;
if (_secondsRemaining <= 0)
{
timerSecondsLeft.Stop();
TimesUp = true;
TimeTextBlock.Text = "Time's up. Press Enter to next problem.";
// HERE WILL INVOKE NEXTQUESTION FUNCTION
}
else
{
TimeTextBlock.Text = string.Format("It remains {0} seconds. Don't take long!", _secondsRemaining);
}
}
}
Look in the code, the comment is this possible ussing delegates?
So you need to do a few things. You have to add some code to you're user control.
// Declare this outside your usercontrol class
public delegate void TimerExpiredEventHandler(object sender, EventArgs e);
This is what needs to be added to your code for the user control.
public partial class TimerUserControl : UserControl
{
public event TimerExpiredEventHandler Expired;
public void OnExpired(EventArgs e)
{
if (Expired != null)
Expired(this, e);
}
public void timerSecondsLeft_Tick(object sender, EventArgs e)
{
_secondsRemaining--;
if (_secondsRemaining <= 0)
{
timerSecondsLeft.Stop();
TimesUp = true;
TimeTextBlock.Text = "Time's up. Press Enter to next problem.";
// Fire the event here.
OnExpired(EventArgs.Empty);
}
else
{
TimeTextBlock.Text = string.Format("It remains {0} seconds. Don't take long!", _secondsRemaining);
}
}
}
Now you need to subscribe to this event inside whatever is calling this usercontrol in the first place.
public partial class ParentForm : Form
{
private void ParentForm_Load(object sender, EventArgs e)
{
var timer = new TimerUserControl();
//Subscribe to the expired event that we defined above.
timer.Expired += new EventArgs(Timer_Expired);
}
public void Timer_Expired(object sender, EventArgs e)
{
//Handle the timer expiring here. Sounds like you are calling another function, so do that here.
}
}
Use the TreeHelper to hunt up the tree for a shared Parent and then down the tree for the User Control you want. Something like this pseudo code:
this.Timer = new System.Windows.Threading.DispatcherTimer
{
Interval = new TimeSpan(0, 0, 1)
};
this.Timer.Tick += (s, e) =>
{
var _Control = s as MyFirstControl;
var _Other = LogicalTreeHelper.GetChildren(_Control.Parent)
.Cast<FrameworkElement>().Where(x => x.Name == "FindIt")
.First<MySecondControl>();
_Other.DoMethod();
};
Best of luck!
i would probably break out the functionality of the timer control here; something like this (note: i am writing this on-the-fly so let me know if it doesn't work as-is, and i will help correct any issues):
// a simple delegate to report the amount of time remaining
// prior to the expiration of the major tick interval; zero
// indicates that this major tick has elapsed.
public delegate void delegateMajorMinorTimerTick
(
int TimeRemaining_sec, ref bool CancelTimer
);
// you could use milliseconds for the interval settings to get
// better granularity, or you could switch to setting the major
// interval instead, however that approach would require a bit
// more checking to make sure the control has sane settings.
public class MajorMinorTimer
{
// this sets the interval in seconds between the
// "minor" ticks used for intermediate processing
// these are the "inner" intervals of the timer
private int myMinorTickInterval_sec;
public int MinorTickInterval_sec
{
get { return myMinorTickInterval_sec; }
}
// this sets the number of minor ticks between the
// expiration of the major interval of the timer.
// the "outer" interval of the timer
private int myMinorTicksPerMajorTick;
public int MinorTicksPerMajorTick
{
get { return myMinorTicksPerMajorTick; }
}
public MajorMinorTimer
(
int parMinorTickInterval_sec,
int parMinorTicksPerMajorTick
)
{
MinorTickInterval_sec = parMinorTickInterval_sec;
MinorTicksPerMajorTick = parMinorTicksPerMajorTick;
}
private DispatcherTimer myBackingTimer;
private int myMinorTickCount;
public void Start()
{
// reset the minor tick count and start the dispatcher
// timer with some reasonable defaults.
myMinorTickCount = 0;
myBackingTimer =
new DispatcherTimer
(
TimeSpan.FromSeconds(MinorTickInterval_sec),
DispatcherPriority.Normal,
new EventHandler(myBackingTimer_Tick),
Dispatcher.CurrentDispatcher
);
myBackingTimer.Start();
}
public event delegateMajorMinorTimerTick onTick;
private bool FireMajorMinorTimerTick(int TimeRemaining_sec)
{
// allows the timer sink to cancel the timer after this
// call; just as an idea, also could be handled with a
// call to Stop() during the event, but this
// simplifies handling a bit (at least to my tastes)
bool CancelTimer = false;
if (onTick != null)
onTick(TimeRemaining_sec, ref CancelTimer);
return CancelTimer;
}
private void myBackingTimer_Tick(object Sender, EventArgs e)
{
// since we are using a DispatchTimer with settings that should
// do not suggest the possibility of synchronization issues,
// we do not provide further thread safety. this could be
// accomplished in the future if necessary with a lock() call or
// Mutex, among other methods.
++myMinorTickCount;
int TicksRemaining = myMinorTickCount - MinorTicksPerMajorTick;
bool Cancel =
FireMajorMinorTimerTick(TicksRemaining * MinorTickInterval_sec);
if (TicksRemaining == 0)
myMinorTickCount = 0;
if (Cancel)
Stop();
}
public void Stop()
{
myBackingTimer.Stop();
}
}
then, assuming, say, a Quiz control, the timer is used like so:
public void QuestionTimerSetup()
{
// sets up a timer to fire a minor tick every second
// with a major interval of 5 seconds
MajorMinorTimer timerQuestion = new MajorMinorTimer(1, 5);
timerQuestion.onTick +=
new delegateMajorMinorTimerTick(QuestionControl_QuestionTimerTick);
}
// ...
public void QuestionControl_OnTick(int TimeRemaining_sec, ref bool CancelTimer)
{
if (TimeRemaining_sec > 0)
{
tblockQuizStatus.Text =
string.Format("There are {0} seconds remaining.", TimeRemaining_sec);
}
else
{
// just for an example
if (NoMoreQuestions)
{
CancelTimer = true;
HandleEndOfQuiz();
tblockQuizStatus.Text =
"Time's up! The quiz is complete!";
}
else
{
tblockQuizStatus.Text =
"Time's up! Press Enter to continue to the next problem.";
}
}
}
another option (rather than, or in addition to, events) in implementing this might be to add an Action taking the time remaining in the major interval for the minor interval action, an Action for the major interval action, and a Func<bool> that checks the stop condition, allowing the user to perform the desired actions in that way. like this:
public class MajorMinorTimer
{
public MajorMinorTimer
(
int parMinorTimerInterval_sec,
int parMinorTicksPerMajorTick,
Action<int> parMinorTickAction,
Action parMajorTickAction,
Func<bool> parShouldStopFunc
)
{
myMinorTimerInterval_sec = parMinorTimerInterval_sec;
myMinorTicksPerMajorTick = parMinorTicksPerMajorTick;
myMinorTickAction = parMinorTickAction;
myMajorTickAction = parMajorTickAction;
myShouldStopFunc = parShouldStopFunc;
}
private Action<int> myMinorTickAction;
private Action myMajorTickAction;
private Func<bool> myShouldStopFunc;
private void myBackingTimer_OnTick()
{
++myMinorTickCount;
int TicksRemaining = myMinorTickCount - MinorTicksPerMajorTick;
if (TicksRemaining == 0)
myMajorTickAction();
else
myMinorTickAction(TicksRemaining * MinorTickInterval_sec);
bool Cancel = myShouldStopFunc();
if (TicksRemaining == 0)
myMinorTickCount = 0;
if (Cancel)
Stop();
}
}
and then in the quiz code instead of hooking up the event do something like:
public void QuestionTimerSetup()
{
MajorMinorTimer timerQuestion =
new MajorMinorTimer
(
1,
5,
// major interval action
(SecsRemaining) =>
{
tblockQuizStatus.Text =
string.Format
(
"There are {0} seconds remaining.", SecsRemaining
);
},
// minor interval action
() =>
{
if (NoMoreQuestions)
{
tblockQuizStatus.Text =
"Time's up! This completes the quiz!";
HandleEndOfQuiz();
}
else
{
tblockQuizStatus.Text =
"Time's up! Press Enter to continue to next question.";
}
},
// timer cancel check function
() =>
IsEndOfQuizHandled()
);
}