Saturday, June 19, 2010

Detect Window Form Application Status

notesLast month, I was working, actually fixing bugs to resolve exceptions, and to improve performance, on an application which is a Windows Form application. One feature client wants is to query data from a SQL server periodically and refresh the UI with the retrieved data. The data may not be changed, but there was no inelegance to know if there was change. Therefore, the UI refresh was enforced because clients want to get the most recent data.

During the process to refactory codes to enhance/optimize SQL queries, I realized that clients wants the more recent data because they want to see any changes when the application is in running. There are many cases this kind of refreshing is actually not necessary. For example, when the window form is minimized, or the OS Windows is locked. In many cases, clients just left the application running after their work. Windows is locked, but the application had still been pulling data from the SQL server.

Then I proposed a optimized strategy to stop the data pulling when the UI is not available, either minimized or Widnows is locked. My suggestion was accepted.

Checking Application Status

To find out the Window from's status is very easy. This can be done by checking form's property WindowState. Here are the codes in a Timer's cycle event:

private void timer1_Tick(object sender, EventArgs e) {
if (IsWindowFormActive()) {
// set interval to max
timer1.Interval = int.MaxValue;
Cursor c = this.Cursor;
this.Cursor = Cursors.WaitCursor;

RefreshDataAndViews();

this.Cursor = c;
// save the last update timestamp to timer's tag
timer1.Tag = DateTime.Now;
// if the window is locked, min or inactive, OnPaint() will not be called.
this.Invalidate();
}
else
{
timer1.Interval = int.MaxValue;
}
}

private bool IsWindowFormActive() {
bool bRet = ( !_windowIsLocked && this.WindowState !=
FormWindowState.Minimized &&
this.Visible && this.Enabled);
return bRet;
}

When the Window Form's state is minimized, Windows is locked or the form is not visible, the timer's interval is set to the maximum number of integer. All the status checking is done within the method IsWindowsActive.

Find out If Windows is Locked

There is one class level variable _windowsIsLocked as a flag for Windows lock status. To set this flag, an event is added within the form's CTOR:

class MainForm {
private bool _windowsIsLocked;
...
// CTOR
public MainForm()
{
...
SystemEvents.SessionSwitch +=
new SessionSwitchEventHandler(SystemEvents_SessionSwitch);
...
}
...
void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionLock)
{
_windowIsLocked = true;
}
else if (e.Reason == SessionSwitchReason.SessionUnlock)
{
// cause OnPaint event called where timer is reset
_windowIsLocked = false;
}
}

Enable Timer When Application Back to Normal Running Status

When the timer's interval is set to the maximum value of integer, how to bring the timer back to the required cycle interval, such as 2 minutes(120000 milliseconds)? The trick is to overwrite form's OnPaint method.

Make sure the base method is called first. This even is the most intensively called event. Whenever the form is activated, clicked, re-sized or moved, OnPaint will be called. Therefore, it is recommended to avoid any lengthy codes in this method. For my case, what I need to do is to reset the timer's interval if the application is back to normal:

protected override void OnPaint(PaintEventArgs e)
{
try
{
base.OnPaint(e);
int interval = NORMAL_REFRESH_INTERVAL;
object timerTag = Timer1.Tag;
// check if the tag was set?
if (timerTag != null && timerTag.ToString().Length > 0)
{
DateTime dt = (DateTime)timerTag;
long ticks = DateTime.Now.Ticks - dt.Ticks;
TimeSpan ts = new TimeSpan(ticks);
// Is this time interval since last refreshing too long?
if (ts.TotalMilliseconds > 1.5 * (double)interval)
{
// secons to start the next timer cycle
interval = QUICKREFRESH_INTERVAL_AFTERAWAKE;
}
// Reset the timer's interval
Timer1.Interval = interval;
}
}
catch { }
}

There is a try and catch blocks and exceptions are ignored. I had several cases of crashes when OnPaint was introduced. Since there are not critical codes there, I just ignore any exceptions from this event.

0 comments: